关于 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. 打个广告...求职中,打包求带走
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

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