122

Is there any built-in function that allows me to deep copy an NSMutableArray?

I looked around, some people say [aMutableArray copyWithZone:nil] works as deep copy. But I tried and it seems to be a shallow copy.

Right now I am manually doing the copy with a for loop:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

but I'd like a cleaner, more succinct solution.

jscs
  • 63,694
  • 13
  • 151
  • 195
ivanTheTerrible
  • 2,836
  • 4
  • 25
  • 25
  • 44
    @Genericrich deep and shallow copies are pretty well defined terms in software development. Google.com may help – Andrew Grant Mar 15 '09 at 04:37
  • 1
    maybe some of the confusion is because the behavior of `-copy` on immutable collections changed between Mac OS X 10.4 and 10.5: http://developer.apple.com/library/mac/releasenotes/Cocoa/FoundationOlder.html#NSFileManager (scroll down to "Immutable collections and copy behavior") – user102008 Jan 14 '11 at 22:37
  • 1
    @AndrewGrant On further thought, and with respect, I disagree that *deep copy* is a well-defined term. Depending upon what source you read, it's unclear whether unlimited recursion into nested data structures is a requirement of a 'deep copy' operation. In other words, you will get conflicting answers on whether a copy operation that creates a new object whose members are shallow copies of the members of the original object is a 'deep copy' operation or not. See http://stackoverflow.com/a/6183597/1709587 for some discussion of this (in a Java context, but it's relevant all the same). – Mark Amery Sep 01 '13 at 21:57
  • @AndrewGrant I have to back up @MarkAmery and @Genericrich. A deep copy is well defined if the root class used in a collection and all its elements is copyable. This is not the case with NSArray (and other objc collections). If an element does not implement `copy`, what shall be put into the "deep copy"? If the element is another collection, `copy` does not actually yield a copy (of the same class). So I think it's perfectly valid to argue about the type of copy wanted in the specific case. – Nikolai Ruhe Oct 12 '15 at 18:40
  • @NikolaiRuhe If an element does not implement `NSCopying`/`-copy`, then it is not copyable— so you should never try to make a copy of it, because that's not a capability it was designed to have.  In terms of Cocoa's implementation, non-copyable objects often have some C backend state they're tied to, so hacking a direct-copy of the object could lead to race conditions or worse.  So to answer _“what shall be put into the ‘deep copy’”_ — A retained ref.  The only thing you can put anywhere when you have a non-`NSCopying` object. – Slipp D. Thompson Feb 11 '17 at 02:18

6 Answers6

215

As the Apple documentation about deep copies explicitly states:

If you only need a one-level-deep copy:

NSMutableArray *newArray = [[NSMutableArray alloc] 
                             initWithArray:oldArray copyItems:YES];

The above code creates a new array whose members are shallow copies of the members of the old array.

Note that if you need to deeply copy an entire nested data structure — what the linked Apple docs call a true deep copy — then this approach will not suffice. Please see the other answers here for that.

Community
  • 1
  • 1
François P.
  • 5,166
  • 4
  • 32
  • 31
  • Seems to be the correct answer. The API states each element gets an [element copyWithZone:] message, which may be what you were seeing. If you are in fact seeing that sending [NSMutableArray copyWithZone:nil] doesn't deep copy, then an array of arrays may not copy correctly using this method. – Ed Marty Mar 15 '09 at 15:02
  • Hm, I got "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Turn copyWithZone:]: unrecognized selector sent to instance 0x4e327a0'" (I have an array of turns). – quantumpotato Mar 21 '11 at 14:45
  • @quantumpotato: You need to implement the `NSCopying` protocol on your `Turn` class. (Yes, I realize this is very late.) – Wevah Jun 08 '11 at 19:33
  • 7
    I don't think this will work as expected. From the Apple docs: "The copyWithZone: method performs a *shallow copy*. If you have a collection of arbitrary depth, passing YES for the flag parameter will perform an immutable copy of the first level below the surface. If you pass NO the mutability of the first level is unaffected. *In either case, the mutability of all deeper levels is unaffected.*" The SO question concerns deep mutable copies. – Joe D'Andrea Oct 27 '11 at 21:23
  • why isn't initWithArray documented in [NSMutableArray Class Reference](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html)? – resting Sep 10 '12 at 02:52
  • 9
    This is an incomplete answer. This results in a one-level deep copy. If there are more complex types within the Array it will not provide a deep copy. – Cameron Lowell Palmer Mar 13 '13 at 09:10
  • 1
    @resting Because it's an `NSArray` method, not unique to `NSMutableArray`. – Mark Amery Sep 01 '13 at 21:43
  • 7
    This *can* be a deep copy, depending on how `copyWithZone:` is implemented on the receiving class. – devios1 Jan 08 '14 at 00:52
64

The only way I know to easily do this is to archive and then immediately unarchive your array. It feels like a bit of a hack, but is actually explicitly suggested in the Apple Documentation on copying collections, which states:

If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol. An example of this technique is shown in Listing 3.

Listing 3 A true deep copy

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];

The catch is that your object must support the NSCoding interface, since this will be used to store/load the data.

Swift 2 Version:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Vahid
  • 3,352
  • 2
  • 34
  • 42
Andrew Grant
  • 58,260
  • 22
  • 130
  • 143
  • 13
    Using NSArchiver and NSUnarchiver is a very heavy solution performance-wise, if your arrays are large. Writing a generic NSArray category method which uses NSCopying protocol will do the trick, causing a simple 'retain' of immutable objects and a real 'copy' of mutable ones. – Nikita Zhuk Mar 15 '09 at 09:15
  • It's good to be cautious about the expense, but would NSCoding really be more expensive than the NSCopying used in the `initWithArray:copyItems:` method? This archiving/unarchiving workaround seems very useful, considering how many control classes conform to NSCoding but not to NSCopying. – Wienke Jul 28 '12 at 01:40
  • I would highly recommend that you never use this approach. Serialization is never faster than just copying memory. – Brett Aug 12 '12 at 23:41
  • 1
    If you have custom objects, make sure to implement encodeWithCoder and initWithCoder so to comply with NSCoding protocol. – user523234 May 02 '13 at 10:36
  • Obviously programmer's discretion is strongly advised when using serialization. – VH-NZZ Feb 26 '15 at 07:27
35

Copy by default gives a shallow copy

That is because calling copy is the same as copyWithZone:NULL also known as copying with the default zone. The copy call does not result in a deep copy. In most cases it would give you a shallow copy, but in any case it depends on the class. For a thorough discussion I recommend the Collections Programming Topics on the Apple Developer site.

initWithArray:CopyItems: gives a one-level deep copy

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding is the Apple recommended way to provide a deep copy

For a true deep copy (Array of Arrays) you will need NSCoding and archive/unarchive the object:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
  • 1
    This is correct. Regardless of popularity or prematurely-optimized edge-cases about memory, performance. As an aside, this ser-derser hack for deep copy is used in many other language environments. Unless there's obj dedupe, this guarantees a good deep copy completely separate from the original. –  Jun 01 '13 at 23:03
  • 1
    Here's a less rottable Apple doc url https://developer.apple.com/library/mac/#documentation/cocoa/conceptual/Collections/Articles/Copying.html –  Jun 01 '13 at 23:04
8

For Dictonary

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

For Array

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);

Darshit Shah
  • 2,366
  • 26
  • 33
5

No, there isn't something built into the frameworks for this. Cocoa collections support shallow copies (with the copy or the arrayWithArray: methods) but don't even talk about a deep copy concept.

This is because "deep copy" starts to become difficult to define as the contents of your collections start including your own custom objects. Does "deep copy" mean every object in the object graph is a unique reference relative to every object in the original object graph?

If there was some hypothetical NSDeepCopying protocol, you could set this up and make decisions in all of your objects, but unfortunately there isn't. If you controlled most of the objects in your graph, you could create this protocol yourself and implement it, but you'd need to add a category to the Foundation classes as necessary.

@AndrewGrant's answer suggesting the use of keyed archiving/unarchiving is a nonperformant but correct and clean way of achieving this for arbitrary objects. This book even goes so far so suggest adding a category to all objects that does exactly that to support deep copying.

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
2

I have a workaround if trying to achieve deep copy for JSON compatible data.

Simply take NSData of NSArray using NSJSONSerialization and then recreate JSON Object, this will create a complete new and fresh copy of NSArray/NSDictionary with new memory references of them.

But make sure the objects of NSArray/NSDictionary and their children must be JSON serializable.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
  • 4,963
  • 2
  • 31
  • 53