关于 OC 中的 copy

首先

iOS开发者都知道, 在写属性的时候, 一般 NSString 的属性都用 copy 去修饰, 那么你们有没有想过, 到底为什么要用 copy, 而这个 copy 又确实起了什么作用呢?

copy 的效果

在 oc 中, copy属性的直接效果是将原对象进行一个 '拷贝', 创建出一个新的副本, 从而达到二者互不干扰的效果.
那么, 请问你们有没有试过, 将 NSString 用 strong 去修饰呢..想必试过的人一定知道后果有多严重吧.(挖坑^.^)

copy 的种类

细心的人会发现, oc 中是支持两种拷贝的 即 copymutableCopy
二者区别如下:

 - 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去修饰的一个大坑.

原因(.填坑)
  1. 由于我们用strong去修饰了title属性, 系统中就只有一份属于title的内存空间
  2. 当我们把 strM赋值给title的时候, 其实就是让两者的指针指向了同一块内存空间
  3. 所以当我们修改了strM的时候, 实际修改的就是strM指针指向的内块内存空间里面的值
  4. 所以当我们打印p.title的时候, 由于titlestrM指向同一块内存空间, 而且里面的值已经被strM修改过了, 所以打印出来的结果就 都变成了 经理三

综上

  1. 由于我们使用的是copy, 而且实际操作的是被新拷贝出来的存储空间, 所以对于原始属性是NSString/NSMutableString并不那么在意
  2. 而在 oc 中, 所有的带mutable的东西都是线程不安全的, 所以在我们实际开发中, 对于NSString属性来说, 我们的规范样式一般是这样样子的
@property (nonatomic, copy) NSString *name;
  1. 现在小伙伴们, 明白为什么我们定义字符串的时候要用 copy 了么?

最后再说两句

  1. 文笔不好, 可能表达逻辑有点混乱, 有任何疑问请留言.
  2. copy 的应用当然不止这些, 下篇文章应该会讲一讲 对于对象的copy, 也即是设计模式中的 原型设计模式
  3. 打个广告...求职中,打包求带走
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,603评论 0 1
  • 淅淅沥沥 下了一夜的雨 清晨 雨珠在橡树的叶上滚动 尘埃,病毒,林立的高烟囱趁着夜色偷偷排放的黄雾 都被雨水冲洗得...
    倪臻阅读 253评论 0 2
  • 秉持 共同进取,团结互助的精神请大家信守以下条列: 上班时间,/早班10:00晚班11:30晚上大家统一下班,时间...
    046839cde4fe阅读 140评论 0 0
  • 这是一篇财富专栏的读书笔记。 说“卖命”是在留言地下看到有人说:提高自己,哪怕卖命也要买得值。对于我们来说,打工相...
    方知方行阅读 251评论 0 1
  • 民国二十九年,正是抗战打得极为艰苦的时候。国土沦丧,百姓流亡。日本人进入了上海,哪怕是十里洋场的大上海,也透着人心...
    青辰阅读 505评论 0 1