首先
iOS开发者都知道, 在写属性的时候, 一般 NSString 的属性都用 copy 去修饰, 那么你们有没有想过, 到底为什么要用 copy, 而这个 copy 又确实起了什么作用呢?
copy 的效果
在 oc 中, copy属性的直接效果是将原对象进行一个 '拷贝', 创建出一个新的副本, 从而达到二者互不干扰的效果.
那么, 请问你们有没有试过, 将 NSString 用 strong 去修饰呢..想必试过的人一定知道后果有多严重吧.(挖坑^.^)
copy 的种类
细心的人会发现, oc 中是支持两种拷贝的 即 copy
和mutableCopy
二者区别如下:
- copy
建立对象的副本 {
如果对象有可变/不可变版本的区别,copy方法,只能拷贝出不可变的版本
如果对象没有可变/不可变的区别,copy方法就是建立一个副本
}
- mutableCopy
建立对象的可变副本(如果对象有"可变/不可变"版本的区别,才需要使用此方法)
--------------------------------------------------------
副本的特点:彼此的内容一样,具有相同的方法
深拷贝 & 浅拷贝
由于我们的 "拷贝"
操作需要区分 可变/不可变
.于是引出了关于 深拷贝&浅拷贝
的问题.
那么如何区分呢?
如下:
- 都会建立新的副本,深拷贝(只要有一个可以修改,就是深拷贝)
可变 -> 可变
可变 -> 不可变
不可变 -> 可变
- 不会建立新的副本,只是引用计数+1,浅拷贝,指针拷贝(两个对象前后都不需要修改)
不可变 -> 不可变
说的简单一点, 对于我们最长使用的 NSString
来说, 只有当不可变字符串拷贝到不可变字符串的时候, 我们的 拷贝
操作才是浅拷贝, 而这恰好是我们开发中最长用到的.
实际应用
模拟环境
首先我们模拟这样一个环境
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSMutableString *title;
@end
我们有这样一个Person模型, 为了区分 可变/不可变
, 分别用 NSString/NSMutableString
来修饰.
由于我们要展示的内容都是基于网络的, 换句话说, 我们要展示的内容都是从网络上获取到以后才展示的.
在控制器里写下如下代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 模拟从网络获取到一个字符串
NSMutableString *strM = [NSMutableString stringWithString:@"BOSS"];
// 将得到的字符串赋值给 Person对象的 title 属性
Person *p = [[Person alloc] init];
p.title = strM;
//分别修改 strM 和 p.title 的字符串内容
[strM setString:@"经理一"];
[p.title setString:@"经理二"];
NSLog(@"----> %@ %@", p.title, strM);
}
各位小伙伴可以自己尝试运行一下这段代码, 猜一下打印结果会是什么?
dang~~~
结果如下
2016-01-04 10:12:28.303 06-Copy属性[10095:669120] -[NSTaggedPointerString setString:]: unrecognized selector sent to instance 0xa00000053534f424
2016-01-04 10:12:28.306 06-Copy属性[10095:669120] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString setString:]: unrecognized selector sent to instance 0xa00000053534f424'
没错. 模拟器果断的崩给我看.. 那么具体是为什么呢 ?
我们逐一排查代码问题, 可以轻松发现 报错的原因就在于
[p.title setString:@"经理二"];
这一句话, 那么我们明明将 title的属性设置为了 NSMutableString, 为什么还在运行的时候报了一个 "未发现 setString:" 方法的错误呢 ?
我们尝试解决一下这个问题:
方法一:
将 title 的描述设置为 strong, 这样再运行就不会报错了.但是这样会造成一个更大的隐患, 这里后面会解释
@property (nonatomic, strong) NSMutableString *title;
方法二:
好吧, 其实没有方法二. 这里就涉及到我上面提过的
如果对象有可变/不可变版本的区别,copy方法,只能拷贝出不可变的版本
所以, 表面上我们修改的是一个我们定义为"NSMutableString"
的 title属性, 但是由于我们用copy
修饰了这个属性, 于是当我们使用p.title = strM
赋值的时候, 其实系统会默认的将原有的title
属性做一次copy
操作.
所以, 我们实际操作的实际上是被copy
出来的一份不可变的字符串, 这也是为什么当我们再调用[p.title setString:@"经理二"];
的时候, 模拟器会崩给我们看的原因.
---------------------------
那么, 如果我们一定要修改原始的title所在的内存空间,要怎么办呢 ? 这就是方法一的解决办法, 用strong去修饰, 这样当我们再修改的时候, 系统不会创建一个备份, 而是直接在原始的内存空间去修改. 这么做的隐患在哪呢? 看下面的代码:
@property (nonatomic, strong) NSMutableString *title;
- (void)viewDidLoad {
[super viewDidLoad];
// 模拟从网络获取到一个字符串
NSMutableString *strM = [NSMutableString stringWithString:@"BOSS"];
// 将得到的字符串赋值给 Person对象的 title 属性
Person *p = [[Person alloc] init];
p.title = strM;
[strM setString:@"经理一"];
[p.title setString:@"经理二"];
NSLog(@"----> %@ %@", p.title, strM);
[strM setString:@"经理三"];
NSLog(@"----> %@ %@", p.title, strM);
}
属性为 strong, 当我们在控制器里加上
[strM setString:@"经理三"];
NSLog(@"----> %@ %@", p.title, strM);
再看看打印出来的结果会是什么?
2016-01-04 10:33:25.878 06-Copy属性[10495:685186] ----> 经理三 经理三
没错, 你会惊奇的发现, 我们只是修改了strM
的值, 为什么p.title
也跟着变了呢?
这就是我上面说的, 当用strong
去修饰的一个大坑.
原因(.填坑)
- 由于我们用
strong
去修饰了title
属性, 系统中就只有一份属于title
的内存空间 - 当我们把
strM
赋值给title
的时候, 其实就是让两者的指针指向了同一块内存空间 - 所以当我们修改了
strM
的时候, 实际修改的就是strM
指针指向的内块内存空间里面的值 - 所以当我们打印
p.title
的时候, 由于title
和strM
指向同一块内存空间, 而且里面的值已经被strM
修改过了, 所以打印出来的结果就 都变成了经理三
综上
- 由于我们使用的是
copy
, 而且实际操作的是被新拷贝出来的存储空间, 所以对于原始属性是NSString/NSMutableString
并不那么在意 - 而在 oc 中, 所有的带
mutable
的东西都是线程不安全的, 所以在我们实际开发中, 对于NSString
属性来说, 我们的规范样式一般是这样样子的
@property (nonatomic, copy) NSString *name;
- 现在小伙伴们, 明白为什么我们定义字符串的时候要用 copy 了么?
最后再说两句
- 文笔不好, 可能表达逻辑有点混乱, 有任何疑问请留言.
- copy 的应用当然不止这些, 下篇文章应该会讲一讲 对于对象的copy, 也即是设计模式中的
原型设计模式
- 打个广告...求职中,打包求带走