iOS 把copy聊透

1、前言

copy这个英文单词,让我第一个想起的是copy忍者卡卡西。我的很是喜欢卡卡西,和谁对战都是五五开的上忍。copy翻译成中文就是复制的意思,为何咱们想要复制呢?我以为缘由有下面几点:面试

  1. 复制更快,重复的东西经过复制,能够快速获得一个如出一辙的东西,好比说一个文件,一段文字,一个忍术什么的。
  2. 更改复制出来的东西,不会影响原来的文件、文字,忍术什么的,这是咱们的目的

那么回到iOS开发,其实类比到生活中也差很少。修改复制出来的东西,不但愿影响原来的内容。objective-c

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

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

等等...app

下面咱们就来探究一下copyatom

2、实战

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
复制代码

从打印结果能够看出:spa

  • 不管是调用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
复制代码

结果分析:3d

  • 毫无疑问,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
复制代码

打印结果分析:code

  • 从数组的内容角度看,数组中的内容都成功被拷贝
  • 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、深拷贝

3、其余问题

上面已经讲清楚了,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];
复制代码

因此最终结果必定是崩溃。

相关文章
相关标签/搜索