iOS中的copy

@property中如何使用内存特性copy和strong

Posted by 齐滇大圣 on March 27, 2016

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

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

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

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

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

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

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

2015-08-30 14:37:50.573 test[19357:5912951] origin string: 0xa000000006362613, 0x7fff50bfbc48
2015-08-30 14:37:50.574 test[19357:5912951] strong string: 0xa000000006362613, 0x7fe44961d790
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"];

输出结果:

2015-08-30 14:51:46.119 test[20229:5955951] origin string: 0x7fc27b47ff60, 0x7fff5c14cc48
2015-08-30 14:51:46.120 test[20229:5955951] strong string: 0x7fc27b47ff60, 0x7fc27b6433a0
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他刚进来的时候有个原价,这是一个原价那当然是不可变咯。

@interface GoodsViewController : UIViewController

@property(nonatomic, strong)NSString *orginPrice;

@end
NSMutableString *_mutablePrice = [NSMutableString stringWithFormat:@"100"];

GoodsViewController *goodsVC = [[GoodsViewController alloc] init];
goodsVC.orginPrice = _mutablePrice;
[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对象。这时如果对这个对象进行可变操作,会产生崩溃。

@property(nonatomic, copy)NSMutableString *copyString;

//这句产生崩溃
[copyString appendString:@"齐滇大圣"];

等价于

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

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

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

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

@implementation PersonModel {
    NSString *_nickName;
}

- (id)copyWithZone:(NSZone *)zone{    
    PersonModel *model = [[[self class] allocWithZone:zone] init];
    model.firstName = self.firstName;
    model.lastName  = self.lastName;
    //未公开的成员
    model->_nickName = _nickName;
    return model;
}

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


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

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

输出值:吴

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


我们修改代码如下:

PersonModel实现NSCopying协议

@interface PersonModel : NSObject<NSCopying>

@property(nonatomic, copy)NSString *firstName;

@end

@implementation PersonModel

- (id)copyWithZone:(NSZone *)zone{
    
    PersonModel *person = [[[self class] allocWithZone:zone] init];
    person.firstName = _firstName;
    return person;
}

@end

PersonModel *person1 = [[PersonModel alloc] init];
person1.firstName = @"郑";
   
PersonModel *person2 = [person1 copy];
person2.firstName = @"吴";
    
NSLog(@"%@",person1.firstName);

输出值:郑