- Published on
Deep Copy and Shallow Copy in iOS
Whether dealing with collection objects or non-collection objects, when they receive copy and mutableCopy messages, the following rules apply:
copyreturns animmutableobject. Therefore, calling mutable interface methods on acopyreturn value will cause a crash.mutableCopyreturns amutableobject.
copy and mutableCopy for Non-Collection Objects
Non-collection system objects refer to types like NSString and NSNumber. Let's start with an example of copying an immutable non-collection object:
NSString *string = @"origin";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
NSLog(@"%p", string);
NSLog(@"%p", stringCopy);
NSLog(@"%p", stringMCopy);
2016-03-10 17:32:04.479 Homework[21715:2353641] 0x1000d0ea0
2016-03-10 17:32:04.481 Homework[21715:2353641] 0x1000d0ea0
2016-03-10 17:32:04.481 Homework[21715:2353641] 0x17006fe40
By examining the memory addresses, we can see that stringCopy and string share the same address — this is a pointer copy. stringMCopy, however, has a different address from string, indicating a content copy.
Now let's look at copying a mutable object:
NSMutableString *string = [NSMutableString stringWithString: @"origin"];
//copy
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
NSLog(@"%p", string);
NSLog(@"%p", stringCopy);
NSLog(@"%p", mStringCopy);
NSLog(@"%p", stringMCopy);
2016-03-10 17:34:11.486 Homework[21728:2354359] 0x17426f800
2016-03-10 17:34:11.487 Homework[21728:2354359] 0x174230600
2016-03-10 17:34:11.487 Homework[21728:2354359] 0x1742306e0
2016-03-10 17:34:11.487 Homework[21728:2354359] 0x174267240
//change value
[mStringCopy appendString:@"mm"]; //crash
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"];
The crash occurs because copy returns an immutable object. After commenting out that line, running the code shows that all four objects — string, stringCopy, mStringCopy, and stringMCopy — have different memory addresses, meaning all of them performed content copies.
For non-collection objects: performing
copyon animmutableobject is a pointer copy;mutableCopyis a content copy. BothcopyandmutableCopyon amutableobject are content copies. In short:
[immutableObject copy] // Shallow copy
[immutableObject mutableCopy] // Deep copy
[mutableObject copy] // Deep copy
[mutableObject mutableCopy] // Deep copy
copy and mutableCopy for Collection Objects
Collection objects refer to types like NSArray, NSDictionary, and NSSet. Let's start with an example of using copy and mutableCopy on an immutable collection:
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
NSLog(@"%p", array);
NSLog(@"%p", copyArray);
NSLog(@"%p", mCopyArray);
2016-03-10 17:53:40.113 Homework[21775:2358227] 0x17403f040
2016-03-10 17:53:40.114 Homework[21775:2358227] 0x17403f040
2016-03-10 17:53:40.114 Homework[21775:2358227] 0x174247e60
We can see that copyArray and array share the same address, while mCopyArray and array have different addresses. This means copy performed a pointer copy, and mutableCopy performed a content copy. However, it's important to note: this content copy only copies the array object itself — the elements inside the array are still pointer copies. This is quite similar to the immutable non-collection case. Let's check whether the same holds for mutable collections:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
NSLog(@"%p", array);
NSLog(@"%p", copyArray);
NSLog(@"%p", mCopyArray);
2016-03-10 17:54:39.114 Homework[21782:2358605] 0x170058e40
2016-03-10 17:54:39.115 Homework[21782:2358605] 0x170058ed0
2016-03-10 17:54:39.115 Homework[21782:2358605] 0x170058ea0
As expected, copyArray, mCopyArray, and array all have different addresses, confirming that both copyArray and mCopyArray performed content copies of array. We can therefore conclude:
For collection objects:
copyon animmutableobject is a pointer copy;mutableCopyis a content copy. BothcopyandmutableCopyon amutableobject are content copies. However: the content copy only applies to the collection object itself — its elements remain pointer copies. In short:[immutableObject copy] // Shallow copy [immutableObject mutableCopy] // One-level-deep copy [mutableObject copy] // One-level-deep copy [mutableObject mutableCopy] // One-level-deep copy
This conclusion is very similar to the non-collection case. But what if you need to copy the elements inside the collection? If you have a multi-level array and perform a content copy on only the first level while the other levels remain pointer copies, is this a deep copy or a shallow copy? On this topic, the Apple documentation states:
This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy, you can explicitly call for one as in Listing 2
Listing 2 Making a deep copy
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
This technique applies to the other collections as well. Use the collection's equivalent of initWithArray:copyItems: with YES as the second parameter.
Apple considers this type of copy not a true deep copy, calling it instead a one-level-deep copy.
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
For a truly complete copy, you need:
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Summary:
-
Shallow copy: Every level of the copied object is a pointer copy.
-
Deep copy (one-level-deep): At least one level of the copied object is a content copy.
-
True deep copy (real-deep copy): Every level of the copied object is a content copy.
Additional note:
NSString *str = @"string";
NSLog(@"%p", str);
str = @"newString";
NSLog(@"%p", str);
2016-03-10 17:58:02.152 Homework[21794:2359359] 0x1000f8ea0
2016-03-10 17:58:02.154 Homework[21794:2359359] 0x1000f8ee0
Here, the memory address is being changed. The second line should be understood as: @"newString" is a new object, and its memory address is assigned to str.