@qidiandasheng
2019-07-04T09:41:05.000000Z
字数 4111
阅读 1808
iOS理论
我们在声明一个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
是深拷贝。那么_strongString
和originString
的内存地址是一样的,_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];
我们先假设orginPrice
为strong
:
我们已经进入GoodsViewController
,这个商品的原价就是100,我们不希望他发生改变。这时可能哪里发了个通知,_mutablePrice
加100
变成了200
。
而GoodsViewController
也接收到了通知,准备把orginPrice
加100
变为200
。但是这时候因为是strong
只是浅拷贝,orginPrice
在_mutablePrice
变为200
的那一刻已经改为200
,这时如果你再加100
,其实orginPrice
就变成300
了,这就不是我们想看到的了。
那如果orginPrice
为copy
呢:
这时发生了深拷贝,_mutablePrice
的改变跟orginPrice
没有关系了,所以不用担心产生上面那样的问题。
因为使用copy
就是深拷贝了一个不可变的NSString
对象。这时如果对这个对象进行可变操作,会产生崩溃。
@property(nonatomic, copy)NSMutableString *copyString;
//这句产生崩溃
[copyString appendString:@"齐滇大圣"];
等价于
NSMutableString *mutableString = [[NSMutableString alloc] initWithFormat:@"我是"];
NSMutableString *copyString = [mutableString copy];
//这句产生崩溃
[mystring appendString:@"齐滇大圣"];
若想令自己所写的对象具有拷贝功能,则需实现 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);
输出值:郑