小结iOS中的copy

  1. 介绍copy和mutableCopy
  1. 介绍深拷贝与浅拷贝
  2. block为什么要用copy
  3. copy相对于直接赋值的好处
  4. 总结

预备知识 :

堆 : 一般由程序员分配释放, 若程序员不释放, 程序结束时可能由OS回收. 注意它与数据结构中的堆是两回事, 分配方式倒是类似于链表.

栈 : 由编译器自动分配释放, 存放函数的参数值, 局部变量的值等. 其操作方式类似于数据结构中的栈.


copy方法和mutableCopy方法

如果你想要创建一个对象, 该对象与源对象的内容一致, 那么你可以考虑用拷贝(copy或mutableCopy), 首先, 我将会利用字符串, 数组, 字典这三种常见的对象来说明copy与mutableCopy的区别.

NSString

NSString *string = @"Jerry";
[string copy] --> 拷贝出内容为Jerry的NSString类型的字符串
[string mutableCopy] --> 拷贝出内容为Jerry的NSMutableString类型的字符串

NSDictionary

NSDictionary *dict = @{@"name" : @"Jerry"};
[dict copy] --> 拷贝出内容与dict相同的NSDictionary类型的字典
[dict mutableCopy] --> 拷贝出内容与dict相同的NSMutableDictionary类型的字典

NSArray

NSArray *array = @[@"Jerry"];
[array copy] --> 拷贝出内容与array相同的NSArray类型的数组
[array mutableCopy] --> 拷贝出内容与array相同的NSMutableArray类型的数组

NOTE:

  • copy拷贝出来的对象类型总是不可变类型(例如, NSString, NSDictionary, NSArray等等)
  • mutableCopy拷贝出来的对象类型总是可变类型(例如, NSMutableString, NSMutableDictionary, NSMutableArray等等)

深拷贝与浅拷贝

何为深拷贝, 何为浅拷贝?

深拷贝 : 拷贝出来的对象与源对象地址不一致! 这意味着我修改拷贝对象的值对源对象的值没有任何影响.
浅拷贝 : 拷贝出来的对象与源对象地址一致! 这意味着我修改拷贝对象的值会直接影响到源对象.

这里需要纠正网上一些错误的观点(以下为错误观点)
<pre>copy都是浅拷贝, mutableCopy都是深拷贝</pre>

我们知道, 当我们用copy从一个可变对象拷贝出一个不可变对象时, 这种情况就属于深拷贝而不是浅拷贝!!

注意 ! 深拷贝与浅拷贝也有相对之分!!!看下面

对于NSString对象, 确实深拷贝就是深拷贝, 浅拷贝就是浅拷贝, 没有任何异议.
但是对于NSArray, NSDictionary, NSSet这些容器类的对象呢? 当然浅拷贝依然是指针拷贝, 那深拷贝意味着连同容器及其容器内的对象一并拷贝吗? 还是只拷贝容器对象, 对容器内的对象则只是简单引用呢? 这里有两种情况, 我姑且把它称为不完全深拷贝完全深拷贝

不完全深拷贝

不完全深拷贝就是只拷贝容器对象(拷贝一个壳), 而对于容器内的对象则只保存一份引用.

不完全深拷贝.png

所以我们知道, 就算我们修改copyArray不会影响到源array, 但是我通过copyArray修改数组内的object, 对应地源array内的object也会随之修改, 大家可以自行测试.

完全深拷贝

完全深拷贝就是连同容器内的对象在内, 完完全全拷贝一份出来

完全深拷贝.png

通过图片可以很清楚地知道, 这种情况下无论是修改copyArray还是通过copyArray修改数组内的object, 对源array都不会造成半点影响.

ps : 默认状态下深拷贝指的是不完全深拷贝, 如要实现完全深拷贝, 则要重写copyWithZone: 方法, 自行实现完全深拷贝的需求. 大体思路如下, 在copyWithZone: 里对象赋值上不直接赋值而是通过copy方法即可实现, 这里不作具体讨论.既然有朋友问到, 那就贴上示例代码吧
// Person.m
- (id)copyWithZone:(NSZone *)zone
{
    Person *cpyPerson = [[Person allocWithZone:zone] init];
    cpyPerson.name = self.name;
    cpyPerson.age = self.age;
    return cpyPerson;
}
// NSArray
- (id)copy
{
    NSArray *cpyArray = [[NSArray alloc] initWithArray:self copyItems:YES];
    return cpyArray;
}
// main.m
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
NSArray *array = @[p1, p2];
NSArray *cpyArray = [array copy];
NSLog(@"%@ - %@", array, cpyArray);
// 输出结果
(
    "<Person: 0x100204af0>",
    "<Person: 0x100206b20>"
) - (
    "<Person: 0x100207910>",
    "<Person: 0x1002074d0>"
)

这样就能办到完全深拷贝的目的了.
ps : 官方文档说明copy方法内部默认会调用copyWithZone方法的, 但是NSArray因为未知的原因导致其copy方法不会调用copyWithZone (可能是因为OC中已经废弃了zone这个概念, 苹果官方文档有说), 所以这里我就利用分类重写了NSArray的copy方法, 实际上苹果并不推荐这么做.


block为什么要用copy?

首先, block是一个对象, 所以block理论上是可以retain/release的. 但是block在创建的时候它的内存是默认是分配在栈(stack)上, 而不是堆(heap)上的. 所以它的作用域仅限创建时候的当前上下文(函数, 方法...), 当你在该作用域外调用该block时, 程序就会崩溃.

Apple官方文档

意思就是 : 一般情况下你不需要自行调用copy或者retain一个block. 只有当你需要在block定义域以外的地方使用时才需要copy. Copy将block从内存栈区移到堆区.

其实block使用copy是MRC留下来的也算是一个传统吧, 在MRC下, 如上述, 在方法中的block创建在栈区, 使用copy就能把他放到堆区, 这样在作用域外调用该block程序就不会崩溃. 但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的, 所以block使用copy还能装装逼, 说明自己是从MRC下走过来的..嘿嘿


copy相对于直接赋值的好处

看看以下代码 :

直接赋值

大家猜猜控制台输出是啥? 是( Kobe ), ( Kobe, McGrady )吗?
错了错了!!!

array = (
    Kobe,
    McGragy
), mArray = (
    Kobe,
    McGragy
)

为什么??? 明明可变数组添加对象是在赋值之后, 为什么后面添加对象还会影响到不可变数组呢??
原因很简单, 因为Objective-C支持多态.
所以表面上self.array是NSArray对象, 其实骨子里是NSMutableArray对象.这样的话将会对后期DEBUG增加很大的成本, 可能会导致莫名其妙的错误.
再看以下代码 :

使用copy

大家再来猜一下输出会是什么?
没错!

array = (
    Kobe
), mArray = (
    Kobe,
    McGragy
)

这样就能保证不管赋值的是可变还是不可变数组, NSArray就是NSArray了!(你爸就是你爸, 不可能变成你了)

所以大家现在知道为什么@property中的NSString, NSArray, NSDictionary属性为什么大多时候用copy而不用strong的原因了么?


总结

这里做出了一张图, 帮助新手弄清楚copy与mutableCopy的区别, 大神请无视_

copy与mutableCopy

如果能够在你的工程中正确使用copy, 将会对你的程序有不小的帮助.细节决定成败嘛!!

欢迎大家关注@Jerry4me, 关注菜鸟成长_. 我会不定时更新一些学习心得与文章.

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

推荐阅读更多精彩内容