深度可变的NSMutableDictionary副本

我想创build一个NSMutableDictionary深层副本,并将其分配给另一个NSMutableDictionary。 字典包含一堆数组,每个数组包含名字,而键是一个字母表(这些名字的第一个字母)。 所以词典中的一个词是'A' – >'Adam','Apple'。 这是我在书中看到的,但我不确定它是否工作:

- (NSMutableDictionary *) mutableDeepCopy { NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity: [self count]]; NSArray *keys = [self allKeys]; for (id key in keys) { id oneValue = [self valueForKey:key]; // should return the array id oneCopy = nil; if ([oneValue respondsToSelector: @selector(mutableDeepCopy)]) { oneCopy = [oneValue mutableDeepCopy]; } if ([oneValue respondsToSelector:@selector(mutableCopy)]) { oneCopy = [oneValue mutableCopy]; } if (oneCopy == nil) // not sure if this is needed { oneCopy = [oneValue copy]; } [ret setValue:oneCopy forKey:key]; //[oneCopy release]; } return ret; } 
  • 应该[onecopy release]在那里还是不在?
  • 以下是我将如何调用这个方法:

    self.namesForAlphabets = [self.allNames mutableDeepCopy];

那会好吗? 还是会造成泄漏? (假设我将self.namesForAlphabets声明为属性,并在dealloc中释放它)。

重要提示 :这个问题(和我下面的代码)都处理一个非常特殊的情况,其中NSMutableDictionary 包含string数组。 这些解决scheme不适用于更复杂的示例。 有关更一般的案例解决scheme,请参阅以下内容

  • 汤姆·达林的回答
  • dreamlax的答案
  • 来自yfujiki GitHub Gist

回答这个特定的情况:

你的代码应该可以工作,但你肯定会需要[oneCopy release] 。 新字典将保留复制的对象,当您将其添加到setValue:forKey ,如果您不调用[oneCopy release] ,则所有这些对象都将保留两次。

一个好的经验法则:如果你allocretaincopy一些东西,你也必须release它。

注意:这里有一些示例代码适用于某些情况 。 这是有效的,因为你的NSMutableDictionary只包含string数组(不需要进一步的深度复制):

 - (NSMutableDictionary *)mutableDeepCopy { NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]]; NSMutableArray * array; for (id key in [self allKeys]) { array = [(NSArray *)[self objectForKey:key] mutableCopy]; [ret setValue:array forKey:key]; [array release]; } return ret; } 

由于免费桥接,您还可以使用CoreFoundation函数CFPropertyListCreateDeepCopy

 NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers); 

假设数组中的所有元素都实现NSCoding协议,则可以通过归档进行深层复制,因为归档将保留对象的可变性。

像这样的东西:

 id DeepCopyViaArchiving(id<NSCoding> anObject) { NSData* archivedData = [NSKeyedArchiver archivedDataWithRootObject:anObject]; return [[NSKeyedUnarchiver unarchiveObjectWithData:archivedData] retain]; } 

不过,这并不是特别有效。

我见过的另一种技术(这种技术效率不高)是使用NSPropertyListSerialization对象来串行化字典,然后对其进行反序列化,但指定了需要可变叶子和容器。

 NSString *errorString = nil; NSData *binData = [NSPropertyListSerialization dataFromPropertyList:self.allNames format:NSPropertyListBinaryFormat_v1_0 errorString:&errorString]; if (errorString) { // Something bad happened [errorString release]; } self.namesForAlphabets = [NSPropertyListSerialization propertyListFromData:binData mutabilityOption:NSPropertyListMutableContainersAndLeaves format:NULL errorDescription:&errorString]; if (errorString) { // something bad happened [errorString release]; } 

再一次,这不是有效的。

试图找出通过检查respondToSelector(@selector(mutableCopy))将不会给所需的结果,因为所有基于NSObject的对象响应此select器(它是NSObject的一部分)。 相反,我们必须查询对象是否符合NSMutableCopying或至lessNSCopying 。 以下是我接受答案中提到的基于这个要点的答案:

对于NSDictionary

 @implementation NSDictionary (MutableDeepCopy) // As seen here (in the comments): https://gist.github.com/yfujiki/1664847 - (NSMutableDictionary *)mutableDeepCopy { NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count]; NSArray *keys = [self allKeys]; for(id key in keys) { id oneValue = [self objectForKey:key]; id oneCopy = nil; if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) { oneCopy = [oneValue mutableDeepCopy]; } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) { oneCopy = [oneValue mutableCopy]; } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){ oneCopy = [oneValue copy]; } else { oneCopy = oneValue; } [returnDict setValue:oneCopy forKey:key]; } return returnDict; } @end 

对于NSArray

 @implementation NSArray (MutableDeepCopy) - (NSMutableArray *)mutableDeepCopy { NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count]; for(id oneValue in self) { id oneCopy = nil; if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) { oneCopy = [oneValue mutableDeepCopy]; } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) { oneCopy = [oneValue mutableCopy]; } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){ oneCopy = [oneValue copy]; } else { oneCopy = oneValue; } [returnArray addObject:oneCopy]; } return returnArray; } @end 

两种方法都有相同的内部复制或不复制逻辑,可以将其抽取到单独的方法中,但为了清楚起见,我将其留在了这里。

以为如果你使用ARC,我会更新一个答案。

Weva提供的解决scheme很好。 现在你可以这样做:

 NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDict, kCFPropertyListMutableContainers)); 

对于ARC – 注意kCFPropertyListMutableContainersAndLeaves真正深度的可变性。

  NSMutableDictionary* mutableDict = (NSMutableDictionary *) CFBridgingRelease( CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)someNSDict, kCFPropertyListMutableContainersAndLeaves)); 

这里有用的答案,但是CFPropertyListCreateDeepCopy不处理数据中的[NSNull null] ,这对于JSON解码数据来说是非常正常的。

我正在使用这个类别:

  #import <Foundation/Foundation.h> @interface NSObject (ATMutableDeepCopy) - (id)mutableDeepCopy; @end 

实现(随意更改/扩展):

  @implementation NSObject (ATMutableDeepCopy) - (id)mutableDeepCopy { return [self copy]; } @end #pragma mark - NSDictionary @implementation NSDictionary (ATMutableDeepCopy) - (id)mutableDeepCopy { return [NSMutableDictionary dictionaryWithObjects:self.allValues.mutableDeepCopy forKeys:self.allKeys.mutableDeepCopy]; } @end #pragma mark - NSArray @implementation NSArray (ATMutableDeepCopy) - (id)mutableDeepCopy { NSMutableArray *const mutableDeepCopy = [NSMutableArray new]; for (id object in self) { [mutableDeepCopy addObject:[object mutableDeepCopy]]; } return mutableDeepCopy; } @end #pragma mark - NSNull @implementation NSNull (ATMutableDeepCopy) - (id)mutableDeepCopy { return self; } @end 

示例扩展名 – string保留为正常副本。 如果你想能够编辑它们,你可以重写这个。 我只需要用深层的字典进行一些testing,所以我没有实现。