Notes and thoughts from Tony

iOS copy 总结

Comments

copy 和 mutableCopy

先说结论,再上代码。

  • copy:对于可变对象,执行 copy 操作后,返回的新的不可变对象,之前的可变对象的引用计数不变。对于不可变对象,执行 copy 操作后,返回的之前的不可变对象,之前的不可变对象的引用计数加一(相当于 retain)。

  • mutableCopy:无论是可变对象还是不可变对象,执行 mutableCopy 操作后,每次都返回新的可变对象,原来对象的引用计数不变。

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

NSLog(@"可变对象:%p", mArr);
printf("mArr 的 retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

NSLog(@"不可变对象:%p", arr);
printf("arr 的 retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));

id a = [mArr copy];

NSLog(@"可变对象 copy 后:%p", a);

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

id b = [arr copy];
NSLog(@"不可变对象 copy 后:%p", b);

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

输出:

2018-07-10 11:06:00.196636+0800 xxxSDK_Example[17207:732959] 可变对象:0x60000044bd60, __NSArrayM
mArr 的 retain count = 2
2018-07-10 11:06:00.197030+0800 xxxSDK_Example[17207:732959] 不可变对象:0x60000044bd90, __NSArrayI
arr 的 retain count = 2
2018-07-10 11:06:00.197397+0800 xxxSDK_Example[17207:732959] 可变对象 copy 后:0x60000044bee0, __NSArrayI
mArr 的 retain count = 2
2018-07-10 11:06:00.197843+0800 xxxSDK_Example[17207:732959] 不可变对象 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(@"可变对象:%p, %@", mArr, NSStringFromClass([mArr class]));
printf("mArr 的 retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(mArr)));

NSLog(@"不可变对象:%p, %@", arr, NSStringFromClass([arr class]));
printf("arr 的 retain count = %ld\n", CFGetRetainCount((__bridge CFTypeRef)(arr)));

id a = [mArr mutableCopy];

NSLog(@"可变对象 mutableCopy 后:%p, %@", a, NSStringFromClass([a class]));

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

id b = [arr mutableCopy];
NSLog(@"不可变对象 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] 可变对象:0x604000455090, __NSArrayM
mArr 的 retain count = 2
2018-07-10 11:03:18.977871+0800 xxxSDK_Example[17122:728787] 不可变对象:0x6040004550c0, __NSArrayI
arr 的 retain count = 2
2018-07-10 11:03:18.978381+0800 xxxSDK_Example[17122:728787] 可变对象 mutableCopy 后:0x604000455120, __NSArrayM
mArr 的 retain count = 2
2018-07-10 11:03:18.978843+0800 xxxSDK_Example[17122:728787] 不可变对象 mutableCopy 后:0x60000025b5a0, __NSArrayM
arr 的 retain count = 2

延伸:

如果可变对象和不可变对象之间相互赋值的话,就会有问题:

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

NSLog(@"mArr 地址:%p, 类型: %@", mArr, NSStringFromClass([mArr class]));

NSArray *a = mArr;
// 以下打印中可以看到,a 所声明的类型和运行时的类型已经不一样了。
NSLog(@"a 地址:%p, 类型: %@", a, NSStringFromClass([a class]));

// 可能出现的问题,误以为 a 是不可变的,但是实际上可以通过 mArr 进行操作。
NSLog(@"a 的值:%@", a);
[mArr addObject:@4];
NSLog(@"操作 mArr 之后 a 的值:%@", a);

NSMutableArray *b = mArr;
NSLog(@"b 地址:%p, 类型: %@", b, NSStringFromClass([b class]));

NSArray *c = arr;
NSLog(@"c 地址:%p, 类型: %@", c, NSStringFromClass([c class]));

NSMutableArray *d = arr;
// 上面那句已经报警告了。
// 以下打印中可以看到,d 所声明的类型和运行时的类型已经不一样了。
NSLog(@"d 地址:%p, 类型: %@", d, NSStringFromClass([d class]));

// 以下代码崩溃,-[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x600000640e10
[d removeAllObjects];
2018-07-10 11:26:35.540207+0800 xxxSDK_Example[17783:762771] mArr 地址:0x604000643a20, 类型: __NSArrayM
2018-07-10 11:26:35.540750+0800 xxxSDK_Example[17783:762771] a 地址:0x604000643a20, 类型: __NSArrayM
2018-07-10 11:26:35.542084+0800 xxxSDK_Example[17783:762771] a 的值:(
    1,
    2,
    3
)

2018-07-10 11:26:35.542918+0800 xxxSDK_Example[17783:762771] 操作 mArr 之后 a 的值:(
    1,
    2,
    3,
    4
)

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

当然,赋值操作中,都没有产生新对象,引用计数也都会加一。

property 中的 copy 和 strong

首先要明确一点,property 声明中的 copy 修饰词和对象方法中 copy 操作不是一个概念。property 声明中的 copy 修饰词意味着,系统自动生成 set 方法中,会对传入的对象进行 copy 操作。

// 以下属性
@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 地址:%p, 类型: %@", mArr, NSStringFromClass([mArr class]));

self.arrStrong = mArr;

NSLog(@"mArr 赋值给 self.arrStrong, 地址:%p, 类型: %@", self.arrStrong, NSStringFromClass([self.arrStrong class]));

self.arrCopy = mArr;

NSLog(@"mArr 赋值给 self.arrCopy, 地址:%p, 类型: %@", self.arrCopy, NSStringFromClass([self.arrCopy class]));

self.mArrStrong = mArr;

NSLog(@"mArr 赋值给 self.mArrStrong, 地址:%p, 类型: %@", self.mArrStrong, NSStringFromClass([self.mArrStrong class]));

self.mArrCopy = mArr;

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

self.arrStrong = arr;

NSLog(@"arr 赋值给 self.arrStrong, 地址:%p, 类型: %@", self.arrStrong, NSStringFromClass([self.arrStrong class]));

self.arrCopy = arr;

NSLog(@"arr 赋值给 self.arrCopy, 地址:%p, 类型: %@", self.arrCopy, NSStringFromClass([self.arrCopy class]));

self.mArrStrong = arr;

NSLog(@"arr 赋值给 self.mArrStrong, 地址:%p, 类型: %@", self.mArrStrong, NSStringFromClass([self.mArrStrong class]));

self.mArrCopy = arr;

NSLog(@"arr 赋值给 self.mArrCopy, 地址:%p, 类型: %@", self.mArrCopy, NSStringFromClass([self.mArrCopy class]));
2018-07-10 12:12:48.472266+0800 xxxSDK_Example[18726:813360] arr 地址:0x600000444a10, 类型: __NSArrayI
2018-07-10 12:12:48.472632+0800 xxxSDK_Example[18726:813360] arr 赋值给 self.arrStrong, 地址:0x600000444a10, 类型: __NSArrayI
2018-07-10 12:12:48.472999+0800 xxxSDK_Example[18726:813360] arr 赋值给 self.arrCopy, 地址:0x600000444a10, 类型: __NSArrayI
2018-07-10 12:12:48.473412+0800 xxxSDK_Example[18726:813360] arr 赋值给 self.mArrStrong, 地址:0x600000444a10, 类型: __NSArrayI
2018-07-10 12:12:48.473836+0800 xxxSDK_Example[18726:813360] arr 赋值给 self.mArrCopy, 地址:0x600000444a10, 类型: __NSArrayI

输出中的地址竟然不变!其实可以理解,arrStrong 和 mArrStrong 就不解释了。这里调用的 set 方法中实际做了 copy 操作,而根据上一节的内容,如果对不可变对象进行 copy,则不产生新对象。

当然要是直接使用 _arrStrong = mArr 这种方式,就跟上一节情况一致了。

注意,当对 copy 修饰的属性重写 set 方法时,也需要对传入的参数进行 copy 操作。

还有一点,以下代码:

NSString *str = @"123";
NSLog(@"不可变对象:%p, %@", str, NSStringFromClass([str class]));
NSString *str2 = @"123";
NSLog(@"不可变对象:%p, %@", str2, NSStringFromClass([str2 class]));
NSString *str3 = [@"123" copy];
NSLog(@"不可变对象:%p, %@", str3, NSStringFromClass([str3 class]));

NSString *str4 = [NSString stringWithFormat:@"123"];
NSLog(@"不可变对象:%p, %@", str4, NSStringFromClass([str4 class]));
NSString *str5 = [NSString stringWithFormat:@"%@", @"123"];
NSLog(@"不可变对象:%p, %@", str5, NSStringFromClass([str5 class]));
NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"123"];
NSLog(@"可变对象:%p, %@", mStr, NSStringFromClass([mStr class]));
NSString *str6 = [mStr copy];
NSLog(@"不可变对象:%p, %@", str6, NSStringFromClass([str6 class]));

输出:

2018-07-10 12:36:37.511135+0800 xxxSDK_Example[19519:853002] 不可变对象:0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.511476+0800 xxxSDK_Example[19519:853002] 不可变对象:0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.511968+0800 xxxSDK_Example[19519:853002] 不可变对象:0x100bdbd70, __NSCFConstantString
2018-07-10 12:36:37.512454+0800 xxxSDK_Example[19519:853002] 不可变对象:0xa000000003332313, NSTaggedPointerString
2018-07-10 12:36:37.513052+0800 xxxSDK_Example[19519:853002] 不可变对象:0xa000000003332313, NSTaggedPointerString
2018-07-10 12:36:37.513583+0800 xxxSDK_Example[19519:853002] 可变对象:0x60000044d950, __NSCFString
2018-07-10 12:36:37.514110+0800 xxxSDK_Example[19519:853002] 不可变对象:0xa000000003332313, NSTaggedPointerString

地址出现一致的原因是因为系统做了优化,只要字符串内容一致,就不会分配新的内存储存。 另外,对于这个类 NSTaggedPointerString,在 64 位系统中新增了 Tagged Pointer 机制,具体可查看这里这里