Published on

iOS copy Summary

copy and mutableCopy

Conclusions first, then code.

  • copy: For a mutable object, calling copy returns a new immutable object, while the retain count of the original mutable object remains unchanged. For an immutable object, calling copy returns the original immutable object, and the retain count of that immutable object is incremented by one (equivalent to retain).

  • mutableCopy: Regardless of whether the object is mutable or immutable, calling mutableCopy always returns a new mutable object, and the retain count of the original object remains unchanged.

// copy
NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
NSArray *arr = [NSArray arrayWithObjects:@4, @5, @6, nil];

NSLog(@"Mutable object: %p", mArr);
printf("mArr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

NSLog(@"Immutable object: %p", arr);
printf("arr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));

id a = [mArr copy];

NSLog(@"Mutable object after copy: %p", a);

printf("mArr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

id b = [arr copy];
NSLog(@"Immutable object after copy: %p", b);

printf("arr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));

Output:

2018-07-10 11:06:00.196636+0800 xxxSDK_Example[17207:732959] Mutable object: 0x60000044bd60, __NSArrayM
mArr retain count = 2
2018-07-10 11:06:00.197030+0800 xxxSDK_Example[17207:732959] Immutable object: 0x60000044bd90, __NSArrayI
arr retain count = 2
2018-07-10 11:06:00.197397+0800 xxxSDK_Example[17207:732959] Mutable object after copy: 0x60000044bee0, __NSArrayI
mArr retain count = 2
2018-07-10 11:06:00.197843+0800 xxxSDK_Example[17207:732959] Immutable object after copy: 0x60000044bd90, __NSArrayI
arr retain count = 3
// mutableCopy
NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
NSArray *arr = [NSArray arrayWithObjects:@4, @5, @6, nil];

NSLog(@"Mutable object: %p, %@", mArr, NSStringFromClass([mArr class]));
printf("mArr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

NSLog(@"Immutable object: %p, %@", arr, NSStringFromClass([arr class]));
printf("arr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));

id a = [mArr mutableCopy];

NSLog(@"Mutable object after mutableCopy: %p, %@", a, NSStringFromClass([a class]));

printf("mArr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

id b = [arr mutableCopy];
NSLog(@"Immutable object after mutableCopy: %p, %@", b, NSStringFromClass([b class]));

printf("arr retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));
2018-07-10 11:03:18.977471+0800 xxxSDK_Example[17122:728787] Mutable object: 0x604000455090, __NSArrayM
mArr retain count = 2
2018-07-10 11:03:18.977871+0800 xxxSDK_Example[17122:728787] Immutable object: 0x6040004550c0, __NSArrayI
arr retain count = 2
2018-07-10 11:03:18.978381+0800 xxxSDK_Example[17122:728787] Mutable object after mutableCopy: 0x604000455120, __NSArrayM
mArr retain count = 2
2018-07-10 11:03:18.978843+0800 xxxSDK_Example[17122:728787] Immutable object after mutableCopy: 0x60000025b5a0, __NSArrayM
arr retain count = 2

Extended notes:

Assigning between mutable and immutable objects can cause problems:

NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
NSArray *arr = [NSArray arrayWithObjects:@4, @5, @6, nil];

NSLog(@"mArr address: %p, type: %@", mArr, NSStringFromClass([mArr class]));

NSArray *a = mArr;
// The following log shows that the declared type of a and its runtime type are different.
NSLog(@"a address: %p, type: %@", a, NSStringFromClass([a class]));

// Potential problem: you might assume a is immutable, but mArr can still modify it.
NSLog(@"a value: %@", a);
[mArr addObject:@4];
NSLog(@"a value after operating on mArr: %@", a);

NSMutableArray *b = mArr;
NSLog(@"b address: %p, type: %@", b, NSStringFromClass([b class]));

NSArray *c = arr;
NSLog(@"c address: %p, type: %@", c, NSStringFromClass([c class]));

NSMutableArray *d = arr;
// The line above already generates a compiler warning.
// The following log shows that the declared type of d and its runtime type are different.
NSLog(@"d address: %p, type: %@", d, NSStringFromClass([d class]));

// The following code crashes: -[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x600000640e10
[d removeAllObjects];
2018-07-10 11:26:35.540207+0800 xxxSDK_Example[17783:762771] mArr address: 0x604000643a20, type: __NSArrayM
2018-07-10 11:26:35.540750+0800 xxxSDK_Example[17783:762771] a address: 0x604000643a20, type: __NSArrayM
2018-07-10 11:26:35.542084+0800 xxxSDK_Example[17783:762771] a value: (
    1,
    2,
    3
)

2018-07-10 11:26:35.542918+0800 xxxSDK_Example[17783:762771] a value after operating on mArr: (
    1,
    2,
    3,
    4
)

2018-07-10 11:26:42.086377+0800 xxxSDK_Example[17783:762771] b address: 0x604000643a20, type: __NSArrayM
2018-07-10 11:26:42.086764+0800 xxxSDK_Example[17783:762771] c address: 0x604000643d20, type: __NSArrayI
2018-07-10 11:26:42.087148+0800 xxxSDK_Example[17783:762771] d address: 0x604000643d20, type: __NSArrayI
2018-07-10 11:26:42.087572+0800 xxxSDK_Example[17783:762771] -[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x604000643d20

Of course, in assignment operations, no new objects are created and the retain count of all objects increases by one.

copy and strong in property declarations

First, one point must be clear: the copy modifier in a property declaration and the copy method call on an object are different concepts. The copy modifier in a property declaration means that the auto-generated setter will perform a copy operation on the incoming object.

// The following properties
@property (nonatomic, strong) NSArray *arrStrong;

@property (nonatomic, copy) NSArray *arrCopy;

@property (nonatomic, strong) NSMutableArray *mArrStrong;

@property (nonatomic, copy) NSMutableArray *mArrCopy;
NSMutableArray *mArr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];

NSLog(@"mArr address: %p, type: %@", mArr, NSStringFromClass([mArr class]));

self.arrStrong = mArr;

NSLog(@"mArr assigned to self.arrStrong, address: %p, type: %@", self.arrStrong, NSStringFromClass([self.arrStrong class]));

self.arrCopy = mArr;

NSLog(@"mArr assigned to self.arrCopy, address: %p, type: %@", self.arrCopy, NSStringFromClass([self.arrCopy class]));

self.mArrStrong = mArr;

NSLog(@"mArr assigned to self.mArrStrong, address: %p, type: %@", self.mArrStrong, NSStringFromClass([self.mArrStrong class]));

self.mArrCopy = mArr;

NSLog(@"mArr assigned to self.mArrCopy, address: %p, type: %@", self.mArrCopy, NSStringFromClass([self.mArrCopy class]));
2018-07-10 11:39:23.288395+0800 xxxSDK_Example[18163:782314] mArr address: 0x604000449bd0, type: __NSArrayM
2018-07-10 11:39:23.289102+0800 xxxSDK_Example[18163:782314] mArr assigned to self.arrStrong, address: 0x604000449bd0, type: __NSArrayM
2018-07-10 11:39:23.289564+0800 xxxSDK_Example[18163:782314] mArr assigned to self.arrCopy, address: 0x604000449d50, type: __NSArrayI
2018-07-10 11:39:23.290061+0800 xxxSDK_Example[18163:782314] mArr assigned to self.mArrStrong, address: 0x604000449bd0, type: __NSArrayM
2018-07-10 11:39:23.290411+0800 xxxSDK_Example[18163:782314] mArr assigned to self.mArrCopy, address: 0x60000044d050, type: __NSArrayI
NSArray *arr = [NSArray arrayWithObjects:@4, @5, @6, nil];
NSLog(@"arr address: %p, type: %@", arr, NSStringFromClass([arr class]));

self.arrStrong = arr;

NSLog(@"arr assigned to self.arrStrong, address: %p, type: %@", self.arrStrong, NSStringFromClass([self.arrStrong class]));

self.arrCopy = arr;

NSLog(@"arr assigned to self.arrCopy, address: %p, type: %@", self.arrCopy, NSStringFromClass([self.arrCopy class]));

self.mArrStrong = arr;

NSLog(@"arr assigned to self.mArrStrong, address: %p, type: %@", self.mArrStrong, NSStringFromClass([self.mArrStrong class]));

self.mArrCopy = arr;

NSLog(@"arr assigned to self.mArrCopy, address: %p, type: %@", self.mArrCopy, NSStringFromClass([self.mArrCopy class]));
2018-07-10 12:12:48.472266+0800 xxxSDK_Example[18726:813360] arr address: 0x600000444a10, type: __NSArrayI
2018-07-10 12:12:48.472632+0800 xxxSDK_Example[18726:813360] arr assigned to self.arrStrong, address: 0x600000444a10, type: __NSArrayI
2018-07-10 12:12:48.472999+0800 xxxSDK_Example[18726:813360] arr assigned to self.arrCopy, address: 0x600000444a10, type: __NSArrayI
2018-07-10 12:12:48.473412+0800 xxxSDK_Example[18726:813360] arr assigned to self.mArrStrong, address: 0x600000444a10, type: __NSArrayI
2018-07-10 12:12:48.473836+0800 xxxSDK_Example[18726:813360] arr assigned to self.mArrCopy, address: 0x600000444a10, type: __NSArrayI

The addresses in the output are actually unchanged! This is understandable: arrStrong and mArrStrong need no explanation. The copy in the setter does perform a copy, but as discussed in the previous section, copying an immutable object does not produce a new object.

Of course, if you use _arrStrong = mArr directly, the situation is the same as in the previous section.

Note: when you override the setter for a copy-modified property, you also need to perform a copy on the incoming parameter.

One more point, the following code:

NSString *str = @"123";
NSLog(@"Immutable object: %p, %@", str, NSStringFromClass([str class]));
NSString *str2 = @"123";
NSLog(@"Immutable object: %p, %@", str2, NSStringFromClass([str2 class]));
NSString *str3 = [@"123" copy];
NSLog(@"Immutable object: %p, %@", str3, NSStringFromClass([str3 class]));

NSString *str4 = [NSString stringWithFormat:@"123"];
NSLog(@"Immutable object: %p, %@", str4, NSStringFromClass([str4 class]));
NSString *str5 = [NSString stringWithFormat:@"%@", @"123"];
NSLog(@"Immutable object: %p, %@", str5, NSStringFromClass([str5 class]));
NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"123"];
NSLog(@"Mutable object: %p, %@", mStr, NSStringFromClass([mStr class]));
NSString *str6 = [mStr copy];
NSLog(@"Immutable object: %p, %@", str6, NSStringFromClass([str6 class]));

Output:

2018-07-10 12:36:37.511135+0800 xxxSDK_Example[19519:853002] Immutable object: 0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.511476+0800 xxxSDK_Example[19519:853002] Immutable object: 0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.511968+0800 xxxSDK_Example[19519:853002] Immutable object: 0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.512454+0800 xxxSDK_Example[19519:853002] Immutable object: 0xa000000003332313, NSTaggedPointerString
2018-07-10 12:36:37.513052+0800 xxxSDK_Example[19519:853002] Immutable object: 0xa000000003332313, NSTaggedPointerString
2018-07-10 12:36:37.513583+0800 xxxSDK_Example[19519:853002] Mutable object: 0x60000044d950, __NSCFString
2018-07-10 12:36:37.514110+0800 xxxSDK_Example[19519:853002] Immutable object: 0xa000000003332313, NSTaggedPointerString

The reason addresses match is that the system performs an optimization: as long as the string content is the same, no new memory will be allocated. Additionally, for the class NSTaggedPointerString, 64-bit systems introduced the Tagged Pointer mechanism. For details, see here and here.