iOS深浅拷贝(纠错)

道歉

之前我的一篇关于深浅拷贝的文章,里面有诸多错误,主要是混淆了混淆copy、mutableCopy和深浅拷贝,给大家带来了误导,这里我深表歉意。

经过大家的指正和参考前辈的文章:http://www.cocoachina.com/bbs/read.php?tid-323045-page-1.html

以及在网上搜集了各种资料,我已经删除了原文,在这里做出了更改,但是肯定还是有纰漏之处,欢迎大家不吝赐教。

理解深浅拷贝

先来看看苹果文档的定义:

There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection.

In the case of these objects, a shallow copy means that a new collection object is created, but the contents of the original collection are not duplicated—only the object references are copied to the new container.
A deep copy duplicates the compound object as well as the contents of all of its contained objects.

再来看看stackoverfolow网友的总结:

Shallow copies duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.

Deep copies duplicate everything. A deep copy of a collection is two collections with all of the elements in the original collection duplicated.

简单来说:

  • 浅拷贝:只是复制容器本身,不会复制容器内部的元素,浅拷贝后生成的新容器对象和原始容器对象共享内部元素
  • 深拷贝:不仅复制容器本身,容器内部的元素也会复制,深拷贝后生成的新容器对象和原始容器的内部元素是独立的。

理解copy和mutableCopy

  • copy:不可变拷贝,遵循NSCopying协议,需要对应实现copyWithZone方法
  • mutableCopy:可变拷贝,遵循NSMutableCopying协议,需要对应实现mutableCopyWithZone:方法

系统容器类NSArray和NSDictonary都已经实现了上述两个协议。


纠错一、混淆copy、mutableCopy和深浅拷贝

网上流传很多的一张图,我也被误导过

image

上图得出了两个错误结论:

  1. 对于不可变或者可变对象的mutableCopy操作都是深拷贝
  1. 对于可变对象的copy操作是深拷贝

这是错的!!!

正确理解

对于不可变或者可变的容器对象的mutableCopy或者copy操作都是浅拷贝!!!

如何验证

那么如何证明呢?

我们先来看看深拷贝的定义:

深拷贝:不仅复制容器本身,容器内部的元素也会复制,深拷贝后生成的新容器对象和原始容器的内部元素是独立的。

那么就可以观察改变源容器的内部元素会不会影响新生成容器的内部元素来证明,影响就说明是浅拷贝,不影响就说明是深拷贝。

1. 代码:
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"one"],
                                [NSMutableString stringWithString:@"two"],
                                [NSMutableString stringWithString:@"three"],
                                [NSMutableString stringWithString:@"four"],
                                nil
                                ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[dataArray2 mutableCopy];
    
    mStr = dataArray2[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

2.输出:
2016-07-31 17:40:30.702 test1[2113:169774] dataArray3:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:40:30.703 test1[2113:169774] dataArray2:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
3.结论:

通过代码可以看到改变了原始数组dataArray2的第一个元素,dataArray3的第一个元素也发生了改变,说明这是浅拷贝。
其他两种情况我就不验证了,大家可以自己验证。你会发现对于可变和不可变的容器对象的copy和mutablCopy操作都是浅复制,区别仅仅是生成的新对象是可变还是不可变的,copy生成不可变对象,mutableCopy生成可变对象!!!!

纠错二:浅拷贝是指针复制,深拷贝是内容复制

同样是流传很广的一张图


image

这句话前半句是错的,后半句是对的。

但是前半句非常迷惑人,而且网上还有证明这句话是对的代码。

如下:

    NSArray *array = @[@1];
    NSLog(@"retainCount:%ld", [array retainCount]);

    NSArray *array2 = [array copy];

    NSLog(@"retainCount:%ld", [array2 retainCount]);
    NSLog(@"内存地址:%p---%p", array,array2);

输出:

2016-08-04 20:25:39.830 test1[27081:776720] retainCount:1
2016-08-04 20:25:39.831 test1[27081:776720] retainCount:2
2016-08-04 20:25:39.831 test1[27081:776720] 内存地址:0x7fa0131175c0---0x7fa0131175c0

可以看到引用计数确实增加了1,而且浅复制前后的内存地址都是一样的,说明是同一个对象,所以得出结论:copy操作只是一次retain操作,对指针进行了复制。

但是真的是这样吗?

真实的情况:

对不可变对象进行copy操作,实际上是把原始对象的的指针的值赋值给生成的新对象的指针的值,这样两个对象的指针的值(对象存放的内存地址)就是相同的,也就导致copy操作之后引用计数增加。

但是怎么证明,我还没找到办法,望大家赐教。

结论:

  • copy操作返回的必然是一个不可变对象,无论源对象是可变对象还是不可变对象。如果源对象是一个不可变对象,那么它们(源对象和新生成的对象)指向同一个对象,如果源对象是可变对象,它们指向不同对象。
  • mutableCopy]返回的必然是一个可变对象,无论源对象是可变对象还是不可变对象,它们(源对象和新生成的对象)仍指向不同地址,是两个对象。
  • copy和mutableCopy都生成新对象

那么既然copy和mutableCopy都无法实现深拷贝,那我们只能自己手动实现了。但是这里要注意深复制还分为单层深拷贝和完全深拷贝,下面具体来看看

单层深拷贝

只会复制容器内部的第一层对象,对于容器内部包含的容器的内部对象不进行复制。

代码:

    NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:
                                 [NSMutableString stringWithString:@"1"],
                                 [NSMutableString stringWithString:@"2"],
                                 [NSMutableString stringWithString:@"3"],
                                 [NSMutableString stringWithString:@"4"],
                                 nil
                                 
                                 ];
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                 [NSMutableString stringWithString:@"one"],
                                 [NSMutableString stringWithString:@"two"],
                                 [NSMutableString stringWithString:@"three"],
                                 [NSMutableString stringWithString:@"four"],
                                 dataArray1,
                                 nil
                                 ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];
    
    mStr = dataArray2[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

输出如下:

2016-07-31 17:45:48.472 test1[2151:173221] dataArray3:(
    one,
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:45:48.472 test1[2151:173221] dataArray2:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)


可以看到dataArray3并没有被改变,但是别高兴的太早,我们再来改改。

代码如下:

    NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"1"],
                                [NSMutableString stringWithString:@"2"],
                                [NSMutableString stringWithString:@"3"],
                                [NSMutableString stringWithString:@"4"],
                                nil
                                
                                ];
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"one"],
                                [NSMutableString stringWithString:@"two"],
                                [NSMutableString stringWithString:@"three"],
                                [NSMutableString stringWithString:@"four"],
                                dataArray1,
                                nil
                                ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];
    
    NSMutableArray *mArr = (NSMutableArray *)dataArray2[4];
    mStr = mArr[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

输出如下:


2016-07-31 17:47:19.421 test1[2174:174714] dataArray3:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)
2016-07-31 17:47:19.421 test1[2174:174714] dataArray2:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)

可以看到深复制又失效了,这是因为dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];仅仅能进行一层深复制,对于第二层容器的内部对象或者更多层容器的内部对象就无效了,那怎么办呢?

这个时候我们需要完全复制

完全复制

要想对多层集合对象进行复制,我们需要进行完全复制,这里可以使用归档和接档。

实现代码如下:

    dataArray3 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:dataArray2]];

此时输出如下:

2016-07-31 17:49:55.561 test1[2202:177163] dataArray3:(
    one,
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:49:55.562 test1[2202:177163] dataArray2:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)

可以看到dataArray3没有被dataArray2的修改影响。


类复制

说完了对象的复制,我们来看看如何实现类的复制,因为比较简单,直接放上代码

定义类复制
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCopying>
@property(strong,nonatomic)NSString *age;
@property(strong,nonatomic)NSString *name;
@end
#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.age = self.age;
    person.name = self.name;
    return person;
}
@end

调用
   Person *person = [[Person alloc]init];
        person.age = @"dsdsd";
        person.name = @"dsdsdddww";
        
        Person *copyPerson = [person copy];
        NSLog(@"%@-----%@",copyPerson.age, copyPerson.name);

可以看到copyPerson的两个属性和persona一样。


@property中的copy关键字

在设置NSString类型的属性的时候,我们最好设置为copy类型,这样别人使用我们定义的属性的时候,他不管怎么改动该属性的赋值,都不会影响我们给该属性赋的值,为什么呢?

下面我们来看看

image

如上图所示,string2的属性是copy类型,可以看到是无法被修改的。

因为此时string2和copystring的内存地址不一样,修改一个,不会影响另外一个。

image

上图所示,如果string2的属性是strong类型,就可以被修改,如下图所示:

因为此时string2和copystring的内存地址都是一样的,修改一个,两个就同时被修改

copy关键字的NSMutableString崩溃

image

原因:

copy关键字的string的setter方法实际上是把参数copy之后再赋值给变量_string,那么此时变量_string虽然被申明为NSMutableString,但是copy之后,就把 变量_string变成了不可变的NSString类型,所以就会出现方法报错,提示对不可变的NSString使用了NSMutableString的方法appendString。

参考文章:

  1. https://en.wikipedia.org/wiki/Object_copying
  2. http://www.cocoachina.com/bbs/read.php?tid-323045-page-1.html
  3. http://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy
  4. C++的:http://www.fredosaurus.com/notes-cpp/oop-condestructors/shallowdeepcopy.html
  5. .Net的:http://www.codeproject.com/Articles/28952/Shallow-Copy-vs-Deep-Copy-in-NET
  6. Java的:http://javapapers.com/core-java/java-clone-shallow-copy-and-deep-copy/
  7. Generic:https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/big3/pages/shallowvsdeep.html
  8. Objective-C:https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.pdf
    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Collections/Collections.pdf
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容