[关闭]
@qidiandasheng 2019-07-04T09:41:05.000000Z 字数 4111 阅读 1808

iOS中的copy

iOS理论


为什么声明NSString属性要使用copy

我们在声明一个NSString属性时,其内存相关的特性,我们有两种选择:strong和copy。一般我们都会使用copy,但是为什么使用copy你知道吗?

稍微了解一点的人可能就会觉得这不就是深拷贝和浅拷贝嘛,使用copy就是深拷贝,使用strong就是浅拷贝。

然而真的是这样吗?下面我们来写一个例子:

  1. @interface TestStringClass ()
  2. @property (nonatomic, strong) NSString *strongString;
  3. @property (nonatomic, copy) NSString *copyedString;
  4. @end
  1. - (void)test {
  2. NSString *originString = [NSString stringWithFormat:@"abc"];
  3. self.strongString = originString;
  4. self.copyedString = originString;
  5. NSLog(@"origin string: %p, %p", originString, &originString);
  6. NSLog(@"strong string: %p, %p", _strongString, &_strongString);
  7. NSLog(@"copy string: %p, %p", _copyedString, &_copyedString);
  8. }

你觉得输出会是什么呢?指针地址肯定是不一样的。普通的想法是认为strong是浅拷贝,copy是深拷贝。那么_strongStringoriginString的内存地址是一样的,_copyedString的内存地址是不一样的。

下面我们来看看实际输出是什么样的:

  1. 2015-08-30 14:37:50.573 test[19357:5912951] origin string: 0xa000000006362613, 0x7fff50bfbc48
  2. 2015-08-30 14:37:50.574 test[19357:5912951] strong string: 0xa000000006362613, 0x7fe44961d790
  3. 2015-08-30 14:37:50.574 test[19357:5912951] copy string: 0xa000000006362613, 0x7fe44961d798

好像跟我们想的不一样?内存地址都是一样的。


下面我们把NSString换成NSMutableString看看,将

NSString *originString = [NSString stringWithFormat:@"abc"];

改为:

NSMutableString *originString = [NSMutableString stringWithFormat:@"abc"];

输出结果:

  1. 2015-08-30 14:51:46.119 test[20229:5955951] origin string: 0x7fc27b47ff60, 0x7fff5c14cc48
  2. 2015-08-30 14:51:46.120 test[20229:5955951] strong string: 0x7fc27b47ff60, 0x7fc27b6433a0
  3. 2015-08-30 14:51:46.120 test[20229:5955951] copy string: 0xa000000006362613, 0x7fc27b6433a8

我们看到originString_strongString内存是一样的,_copyedString内存地址是不一样的。

我们现在来想一下原因,当我们使用NSString的时候其实是不希望他改变的,那么我们一般情况下是使用copy,希望他进行深拷贝,那源字符串修改就不会影响到_copyedString了。但是如果源字符串也是NSString不可变的呢,那其实就算是浅拷贝也不会有什么影响了。
所以系统可能就在当源字符串为不可变类型时,你属性的内存特性为copy其实也只进行浅拷贝。当源属性为可变类型时,才进行深拷贝。


所以我们建议在使用NSString属性时使用copy,避免可变字符串的修改导致的一些非预期问题。

上面这句话我们会常常看到,那么很多人问我了,这种情况什么情景下会出现呢?

我这里举一个最简单的例子,有个ViewController他刚进来的时候有个原价,这是一个原价那当然是不可变咯。

  1. @interface GoodsViewController : UIViewController
  2. @property(nonatomic, strong)NSString *orginPrice;
  3. @end
  1. NSMutableString *_mutablePrice = [NSMutableString stringWithFormat:@"100"];
  2. GoodsViewController *goodsVC = [[GoodsViewController alloc] init];
  3. goodsVC.orginPrice = _mutablePrice;
  4. [self.navigationController pushViewController:goodsVC animated:YES];

我们先假设orginPricestrong
我们已经进入GoodsViewController,这个商品的原价就是100,我们不希望他发生改变。这时可能哪里发了个通知,_mutablePrice100变成了200
GoodsViewController也接收到了通知,准备把orginPrice100变为200。但是这时候因为是strong只是浅拷贝,orginPrice_mutablePrice变为200的那一刻已经改为200,这时如果你再加100,其实orginPrice就变成300了,这就不是我们想看到的了。

那如果orginPricecopy呢:
这时发生了深拷贝,_mutablePrice的改变跟orginPrice没有关系了,所以不用担心产生上面那样的问题。

为什么声明NSMutableString属性不能用copy

因为使用copy就是深拷贝了一个不可变的NSString对象。这时如果对这个对象进行可变操作,会产生崩溃。

  1. @property(nonatomic, copy)NSMutableString *copyString;
  2. //这句产生崩溃
  3. [copyString appendString:@"齐滇大圣"];

等价于

  1. NSMutableString *mutableString = [[NSMutableString alloc] initWithFormat:@"我是"];
  2. NSMutableString *copyString = [mutableString copy];
  3. //这句产生崩溃
  4. [mystring appendString:@"齐滇大圣"];

如何让自己的类用 copy 修饰符

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。

NSCopying协议中的声明的方法只有一个- (id)copyWithZone:(NSZone *)zone。当我们的类实现了NSCopying协议,通过类的对象调用copy方法时,copy方法就会去调用我们实现的- (id)copyWithZone:(NSZone *)zone方法,实现拷贝功能。实现代码如下所示:

  1. @implementation PersonModel {
  2. NSString *_nickName;
  3. }
  4. - (id)copyWithZone:(NSZone *)zone{
  5. PersonModel *model = [[[self class] allocWithZone:zone] init];
  6. model.firstName = self.firstName;
  7. model.lastName = self.lastName;
  8. //未公开的成员
  9. model->_nickName = _nickName;
  10. return model;
  11. }

NSMutableCopying中对于的声明方法为- (id)mutableCopyWithZone:(NSZone *)zone。跟NSCopying的区别就是返回的对象是否是可变类型。


下面我们来写个例子看看如何运用:

  1. PersonModel *person1 = [[PersonModel alloc] init];
  2. person1.firstName = @"郑";
  3. PersonModel *person2 = person1;
  4. person2.firstName = @"吴";
  5. NSLog(@"%@",person1.firstName);

输出值:吴

因为这个person1对象根本没有被深拷贝,所有person2改变的时候,person1也被改变了。


我们修改代码如下:

PersonModel实现NSCopying协议

  1. @interface PersonModel : NSObject<NSCopying>
  2. @property(nonatomic, copy)NSString *firstName;
  3. @end
  4. @implementation PersonModel
  5. - (id)copyWithZone:(NSZone *)zone{
  6. PersonModel *person = [[[self class] allocWithZone:zone] init];
  7. person.firstName = _firstName;
  8. return person;
  9. }
  10. @end
  1. PersonModel *person1 = [[PersonModel alloc] init];
  2. person1.firstName = @"郑";
  3. PersonModel *person2 = [person1 copy];
  4. person2.firstName = @"吴";
  5. NSLog(@"%@",person1.firstName);

输出值:郑

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注