深度可变的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]
,则所有这些对象都将保留两次。
一个好的经验法则:如果你alloc
, retain
或copy
一些东西,你也必须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,所以我没有实现。