2009-12-23 113 views
36

我想创建一个NSMutableDictionary的深层副本并将其分配给另一个NSMutableDictionary。该字典包含一堆数组,每个数组包含名称,键是一个字母表(这些名称的第一个字母)。因此,字典中的一个条目是'A' - >'Adam','Apple'。下面是我在一本书中看到的,但我不知道它的工作原理:一个NSMutableDictionary的深层可变副本

- (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发行]是有或没有?
  • 这里是我要如何调用此方法:

    self.namesForAlphabets = [self.allNames mutableDeepCopy];

可以吗?或者它会导致泄漏? (假设我将self.namesForAlphabets声明为属性,并在dealloc中释放它)。

回答

9

重要:这个问题(及以下我的代码)都处理一个非常具体的情况下,其中的NSMutableDictionary包含阵列串。对于更复杂的示例,这些解决方案将不会工作。对于更一般情况下的解决方案,请参阅以下内容:


回答这个特定的情况下:

你的代码应工作,但是你一定需要[oneCopy release]。新字典将在您将它们与setValue:forKey一起添加时保留复制的对象,因此如果您没有拨打[oneCopy release],则所有这些对象都将保留两次。

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

注:此处是一些示例代码,将工作某些情况下,只有。这工作,因为你的NSMutableDictionary包含(不需要进一步的深度复制)字符串只有数组:

- (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; 
} 
+1

谢谢,但“副本”使新的字典中的NSArray(或NSMutableArray)不可变。所以这是行不通的。 – 2009-12-23 04:54:40

+3

用'mutableCopy'代替'copy',这很好。 – 2009-12-23 05:25:32

+0

啊,是的。抱歉!那肯定是'mutableCopy'。我在我的答案中做了修改。 – 2009-12-23 14:59:11

8

,我已经看到(这是不是在所有非常有效),另一种方法是使用一个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]; 
} 

再次,这是不是在所有有效。

+1

为什么这种方法效率低下? – 2013-09-23 23:19:43

11

假设阵列的所有元素都实现NSCoding协议,您可以通过归档进行深度复制,因为归档会保留对象的可变性。

事情是这样的:

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

这不是特别有效,但。

+0

此方法是否返回可变深度副本或不可变的深度副本? – dreamlax 2009-12-23 03:04:43

+2

无论你把它放进去。如果你放入一个NSMutableArray,你会得到一个NSMutableArray。 – 2009-12-23 04:28:21

+0

@dreamlax如果没有,你可以用'mutableCopy'创建一个可变版本。 – 2015-02-10 14:57:03

72

因为免费桥接的,你也可以使用的CoreFoundation功能CFPropertyListCreateDeepCopy

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

很酷。这很好。除了由于某种原因,它在性能工具中注册为泄漏。任何想法是什么? – Jonah 2010-08-25 13:54:35

+2

它遵循Core Foundation的“创建”规则,所以你需要确保你释放或者自动释放返回的字典(或者如果你想保留它,就不要保留它)。 – Wevah 2010-08-25 16:01:19

+0

所以看来你必须打电话:CFRelease(mutableCopy);当你需要释放对象时。谢谢! – Jonah 2010-08-25 23:40:53

3

试图通过检查respondToSelector(@selector(mutableCopy))不会取得理想的结果找出所有NSObject基于对象对此作出回应选择器(它是NSObject的一部分)。相反,我们必须查询对象是否符合NSMutableCopying或至少NSCopying。这里的基础上公认的答案中提到this gist我的回答:

对于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 

这两种方法都具有相同的内部 - 复制 - 或 - 不可─并且可以将其提取到一个单独的方法中,但为了清楚起见,我将其保留为这样。

1

想到如果您使用ARC,我会更新一个答案。

Weva提供的解决方案很好。现在你可以做到这一点是这样的:

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

kCFPropertyListMutableContainersAndLeaves也许也是你想要的,这使得可变性更深。 – 2015-09-23 21:36:55

1

对于ARC - 注意,真正的深可变性kCFPropertyListMutableContainersAndLeaves。这里

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

没有为我工作 - mutableDict总是零。 – 2015-10-12 17:19:24

0

有用的答案,但CFPropertyListCreateDeepCopy不处理数据,这与JSON非常正常[NSNull null]解码后的数据,例如。

我使用这个类:

#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 

例扩展 - 串留作正常拷贝。如果你想能够编辑它们,你可以覆盖它。我只需要用深层字典进行一些测试,所以我没有实现。