iOS 把copy聊透

微信扫一扫,分享到朋友圈

iOS 把copy聊透

copy 这个英文单词,让我第一个想起的是copy忍者卡卡西。个人非常喜欢卡卡西,和谁对战都是五五开的上忍。 copy 翻译成中文就是复制的意思,为什么我们想要复制呢?我觉得原因有下面几点:

  1. 复制更快,重复的东西通过复制,可以快速得到一个一模一样的东西,比如说一个文件,一段文字,一个忍术什么的。
  2. 更改复制出来的东西,不会影响原来的文件、文字,忍术什么的,这是我们的目的

那么回到iOS开发,其实类比到生活中也差不多。修改复制出来的东西,不希望影响原来的内容。

针对 copy 就会引出一些面试题:

  1. 定义一个 NSString 属性,可以使用 strong 关键字修饰吗?如果可以,什么时候使用 strong 修饰,什么时候使用 copy 修饰?
  2. 定义一个 NSMutableArray 属性,关键字使用 copy ,像 NSMutableArray 中添加元素会发生什么现象?
  3. 涉及到深拷贝,浅拷贝的, NSString NSMutableString NSArray NSMutableArray NSDictionary NSMutableDictionary 调用 copy 方法或者 mutableCopy 方法,是深拷贝还是浅拷贝?

等等…

下面我们就来探究一下 copy

二、实战

2.1 案例1 NSString

  • 在写案例之前,我们应该明确一点, NSString 这个类代表 不可变字符串 。不可变意味着创建出来的字符串对象不可以被修改
  • 创建一个字符串对象”test”, str1 指向字符串对象
  • str1 调用 copy 方法, str2 指向 copy 出来的对象
  • str1 调用 mutableCopy 方法, str3 指向 mutableCopy 出来的对象
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str1 = @"test";
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"n str1 -> %@ n str2 -> %@ n str3 -> %@", str1, str2, str3);
NSLog(@"n str1 -> %p n str2 -> %p n str3 -> %p", str1, str2, str3);
}
// 打印结果:
2020-06-04 22:47:42.843279+0800 05_copy[34618:3042446]
str1 -> test
str2 -> test
str3 -> test
2020-06-04 22:47:42.843496+0800 05_copy[34618:3042446]
str1 -> 0x10c9a0020
str2 -> 0x10c9a0020
str3 -> 0x600001836fa0
复制代码

从打印结果可以看出:

  • 无论是调用 copy 还是 mutaleCopy 方法都成功复制了”test“这个文本
  • 从打印内存地址可以看出 str1str2 都指向了同一个对象, str3 指向了另外一个对象。

画图分析一波:

  • 结合上面的内存图, str1st2 都指向了同一个对象, str3 指向了另外一个对象。
  • 为什么会出现这样的现象呢?因为调用 copy 方法会返回一个 不可变对象 ,而调用 mutableCopy 方法会返回一个 可变对象
  • 返回不可变对象,就意味着无法修改,所以 copy 执行完毕之后,完全可以指向之前的对象,反正没办法进行修改,这样反而节省了内存空间。
  • 返回可变对象,就意味着我们有修改字符串的需求,只有创建新的对象,修改字符串的时候才不会影响之前字符串的值。
  • 从而得出一个结论:使用 NSString 创建的对象,调用 copy 方法不会创建新的对象,只是指针的拷贝,属于浅拷贝。而调用 mutableCopy 方法会创建一个与之前内容一样的新的对象,属于深拷贝。

2.2 案例2 NSMutableString

  • 创建一个可变字符串对象,内容是”test”,用 str1 指向该对象
  • str1 调用 copy 方法,使用 str2 指向返回的对象
  • str1 调用 mutableCopy 方法,使用 str3 指向返回的对象
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"test"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"n str1 -> %@ n str2 -> %@ n str3 -> %@", str1, str2, str3);
NSLog(@"n str1 -> %p n str2 -> %p n str3 -> %p", str1, str2, str3);
}
// 打印结果:
2020-06-05 12:16:42.174359+0800 05_copy[35192:3110215]
str1 -> test
str2 -> test
str3 -> test
2020-06-05 12:16:42.174536+0800 05_copy[35192:3110215]
str1 -> 0x600003d282d0
str2 -> 0xbf8fdf3650605176
str3 -> 0x600003d28270
复制代码

结果分析:

  • 毫无疑问, st1str2str3 的内容都是”test“,成功复制
  • 发现,三个指针存储的内存地址不同,说明产生了新的对象

画图分析:

  • st1 是指向的是可变字符串,可以进行修改
  • str1调用 copy 方法会重新创建一个新的不可变字符串,是深拷贝,当 str1 进行修改的时候, str2`中的值不会受到任何的影响
  • str1 调用 mutableCopy 方法会创建一个新的可以变字符串,那么就可以对这个可变字符串进行修改,不影响其 str1str2 ,是深拷贝。并且三者互不影响。

2.3 案例三 NSArray和NSMutableArray

2.3.1 NSArray

  • 操作和上面 NSString 类似,就不再说明了,直接看代码
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = [array1 copy];
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"n array1 -> %@ n array2 -> %@ n array3 -> %@", array1, array2, array3);
NSLog(@"n array1 -> %p n array2 -> %p n array3 -> %p", array1, array2, array3);
}
// 打印结果:
2020-06-05 12:32:17.303200+0800 05_copy[35231:3117262]
array1 -> (
a,
b,
c
)
array2 -> (
a,
b,
c
)
array3 -> (
a,
b,
c
)
2020-06-05 12:32:17.303393+0800 05_copy[35231:3117262]
array1 -> 0x600003a0c360
array2 -> 0x600003a0c360
array3 -> 0x600003a0c0f0
复制代码

打印结果分析:

  • 从数组的内容角度看,数组中的内容都成功被拷贝
  • array1 array2array3 存储的地址值来看,调用 copy 方法进行了浅拷贝,而调用 mutableCopy 方法是深拷贝。也就是说,修改 array4 里面的值不会影响 array1array2

2.3.2 NSMutableArray

- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
NSArray *array2 = [array1 copy];
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"n array1 -> %@ n array2 -> %@ n array3 -> %@", array1, array2, array3);
NSLog(@"n array1 -> %p n array2 -> %p n array3 -> %p", array1, array2, array3);
}
// 打印结果:
2020-06-05 12:37:57.830060+0800 05_copy[35251:3120031]
array1 -> (
a,
b,
c
)
array2 -> (
a,
b,
c
)
array3 -> (
a,
b,
c
)
2020-06-05 12:37:57.830295+0800 05_copy[35251:3120031]
array1 -> 0x60000378d6e0
array2 -> 0x60000378d950
array3 -> 0x60000378d9b0
复制代码

结果分析:

  • 无论调用 copy 还是 mutable 方法都是深拷贝

总结:

在OC中,其实还有很多类似的类比如 NSDictionary NSMutableDictionary NSSet NSMutableSet 结论都是一样的。可以自己去敲一段代码来验证。常用的我总结如下表:

copy mutableCopy
NSString 返回NSString、浅拷贝 返回NSMutableString、深拷贝
NSMutableString 返回NSString、深拷贝 返回NSMutableString、深拷贝
NSArray 返回NSArray、浅拷贝 返回NSMutableArray、深拷贝
NSMutableArray 返回NSArray、深拷贝 返回NSMutableArray、深拷贝
NSDictionary 返回NSDictionary、浅拷贝 返回NSMutableDictionary、深拷贝
NSMutableDictionary 返回NSDictionary、深拷贝 返回NSMutableDictionary、深拷贝

三、其他问题

上面已经讲清楚了, copymutableCopy 针对于不同的类返回结果以及是否产生新的对象做了分析和总结。

还遗留了点问题

  1. 在一个中类定义一个 NSString 属性的时候, NSString 通常定义为 copy 定义成 strong 行不行?如果两者都行,开发中该使用哪一个?
  2. 定义一个 NSMutableArray NSMutableString NSMutableDictionary 的属性,能不能用 copy ,会不会有什么问题?

3.1 问题1

  • 如下代码打印结果是什么?如果把定义属性的 copy 修改成 strong ,那么打印结果又是什么呢?
@interface ViewController ()
@property (nonatomic, copy) NSString *str;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"test"];
self.str = mStr;
[mStr appendString:@"haha"];
NSLog(@"mStr -> %@", mStr);
NSLog(@"self.str -> %@", self.str);
}
复制代码
  • copy 关键字修饰的打印结果:
mstr -> testhaha
self.str -> test
复制代码
  • strong 关键字修饰的打印结果:
mstr -> testhaha
self.str -> testhaha
复制代码

这个问题的本质在于这句代码, 这句代码的本质呢是在调用str的setter方法 ,最关键的地方就是要知道setter方法的内部是怎么写的呢?

self.str = mStr;
复制代码
- (void)setStr:(NSString *)str {
if (_str != str) {
[_str release];
_str = [str copy]; // 最关键的地方
}
}
复制代码
  • 上面写的这个setter方法是抛开ARC环境下的写法,如果传入的新值和之前保存的值不一致,就先将老的值引用计数-1,新值调用 copy 方法,赋值给成员变量。
  • 传入的str是什么?传入的 str 就是 mstr ,将一个可变字符串进行 copy 后会创建一个新的对象, _str 指向了一个新的对象。所以你再去修改曾经的 mStr 的值不会影响 _str 的值。

如果定义字符串属性的时候,使用 strong 关键字呢?还是从本质出发,setter方法里的这句代码变了。变成 retain 了。所以 _str 指向原来的位置,当你修改mStr的值时候,_str肯定会跟着改变。

_str = [str retain]; // 最关键的地方
复制代码

如果你还不懂,再画个图给你解释:

3.2 问题2

  • 下面代码打印结果是什么?
@interface ViewController ()
@property (nonatomic, copy) NSMutableArray *mArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mArray = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
[self.mArray addObject:@"d"];
NSLog(@"%@", self.mArray);
}
复制代码
  • 没有打印,直接崩溃
  • 错误信息:__NSArrayI找不到 addObject方法,
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600002704060
复制代码
  • 根据问题1的经验,你细品,下面这句代码的本质是什么?
  • 就是在调用setter方法,由于是 copy 修饰,会创建一个新对象,而新对象是不可变数组,不可变数组调用 addObject 方法怎么可能找得到呢?
self.mArray = [[NSMutableArray alloc] initWithObjects:@"a", @"b", @"c", nil];
复制代码

所以最终结果一定是崩溃。

实力宠粉! 6·5京东超级品牌日海量爆品尖货6.1…

上一篇

一次奇葩面试经历,没去过BAT的Android开发就不配面试架构师?

下一篇

你也可能喜欢

iOS 把copy聊透

长按储存图像,分享给朋友