[iOS 内存管理] 浅拷贝(Shallow Copy)与深拷贝(Deep Copy)

概念

拷贝的方式有两种:浅拷贝(Shallow Copy)深拷贝(Deep Copy)。 从字面意思理解,浅拷贝,只是拷贝了对象的指针,而不是拷贝对象本身。 深拷贝,是直接拷贝整个对象的内存到另一块内存中。

如下图所示:左侧是浅拷贝,右侧是深拷贝

  • 浅拷贝就是拷贝对象的指针,深拷贝就是拷贝对象本身。
image.png

拷贝操作

在Objective-C中,通过两个方法 copymutableCopy可以执行拷贝操作,其中copy是获得一个不可变对象,而mutableCopy是获得一个可变对象。并且两个方法分别调用copyWithZonemutableCopyWithZone两个方法来进行拷贝操作,一个类必须实现copyWithZone或者mutableCopyWithZone,才能进行copy或者mutableCopy

拷贝的类型

浅拷贝(shallow copy)

浅拷贝有很多中方法,当你进行浅拷贝时,会向原始的集合发送retain消息,这时引用计数就会 +1 ,同时指针就被拷贝到新的集合中去。
下面是一个实现浅拷贝的例子:

NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];

深拷贝(deep copy)

集合的深拷贝有两种方法。

  • 可以用initWithArray:copyItems: 将第二个参数设置为YES即可深拷贝,如:
NSDictionary shallowCopyDict = [[NSDictionary alloc]initWithDictionary:someDictionary copyItems:YES];

如果你用这种方法深拷贝,集合里的每个对象都会收到copyWithZone:消息。如果集合里的对象都遵循NSCopying 协议,那么对象就会被深拷贝到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深拷贝,会在运行时出错。copyWithZone: 这种拷贝方式只能够提供单层内存拷贝(one-level-deep copy),而非真正的深拷贝。

  • 第二个方法是将集合进行归档(archive),然后解档(unarchive),

如:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
    这里需要注意:
* 第一种方式copyWithZone:这种拷贝方式只能够提供单层内存拷贝(one-level-deep copy),而非真正的深拷贝。
* 第二种方式归档和解档才是实现真正的深拷贝。

one-level-deep copy 集合的单层深拷贝

这里需要区分一个概念性的问题:
如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深拷贝,还是浅拷贝?对此,苹果官网文档有这样一句话描述:

This kind of copy is only capable of producing a one-level-deep copy.
If you only need a one-level-deep copy, you can explicitly call for one as in Listing 

从文中可以看出,苹果认为这种拷贝不是真正的深拷贝,而是将其称为单层深拷贝(one-level-deep copy)。因此,网上有人对浅拷贝、深拷贝、单层深拷贝做了概念区分。

* 浅拷贝(shallow copy):在浅拷贝操作时,对于被拷贝对象的每一层都是指针拷贝。
* 深拷贝(one-level-deep copy):在深拷贝操作时,对于被拷贝对象,至少有一层是深拷贝。
* 完全拷贝(real-deep copy):在完全拷贝操作时,对于被拷贝对象的每一层都是对象拷贝

当然,这些都是概念性的东西。掌握住最核心的问题是

进行拷贝操作时,被拷贝的是指针还是内容即可

系统对象的拷贝

不管是集合类对象,还是非集合类对象,当接收到copy和mutableCopy消息时,都遵循以下准则:

  • copy返回不可变对象(immutable);所以,如果对copy返回值使用mutable对象接口就会crash;
  • mutableCopy返回可变对象(mutable);
    下面将针对非集合类对象和集合类对象的copy和mutableCopy方法进行具体的阐述

非集合对象的copy和mutableCopy

系统非集合类对象指的是 NSString, NSNumber ... 之类的对象。下面先看个非集合类immutable对象拷贝的例子

非集合&不可变

  NSString* str = @"test string";
  NSString* strCy = [str copy];
  NSMutableString* strMCy = [strCy mutableCopy];
  //! 打印输出
  NSLog(@"   str :%p  %p", str, &str);
  NSLog(@" strCy :%p  %p", strCy, &strCy);
  NSLog(@"strMCy :%p  %p", strMCy, &strMCy);
  //!
     str :0x100001040  0x7fff5fbff7e8
   strCy :0x100001040  0x7fff5fbff7e0
  strMCy :0x1004002f0  0x7fff5fbff7d8

可见

  1. str和strCy的地址是相同的,所以进行了指针拷贝即浅拷贝
  2. str和strMCy的地址是不同的,所以进行了内容拷贝即深拷贝

非集合&可变

        //! Test 1
        NSMutableString* str = [NSMutableString stringWithString:@"m1Str test"];
        NSString* strCy = [str copy];
        NSMutableString* mStrCy = [str copy];
        NSMutableString* mStrMCy = [str mutableCopy];

        NSLog(@"    str :%p         %p", str, &str);
        NSLog(@"  strCy :%p     %p", strCy, &strCy);
        NSLog(@" mStrCy :%p     %p", mStrCy, &mStrCy);
        NSLog(@"mStrMCy :%p         %p", mStrMCy, &mStrMCy);
      //!
            str :0x100308bd0            0x7fff5fbff7e8
          strCy :0xdea10af20184a5       0x7fff5fbff7e0
         mStrCy :0xdea10af20184a5       0x7fff5fbff7d8
        mStrMCy :0x100308df0            0x7fff5fbff7d0

          //! Test 2
        [mStrCy appendString:@"mstr append"];  // **Crash**
        [str appendString:@" str  "];
        [mStrMCy appendString:@" mStrMCy "];
  1. 从Test 1可以看出非集合&可变对象无论是copy还是mutableCopy,都是内容拷贝深拷贝。
  2. Test 2中会出现Crash,原因就是因为mStrCy虽然是可变对象,但是所对应的内容是copy而来的不可变对象。

非集合拷贝结论

从上述三个实验可以看出

非集合 copy mutableCopy
不可变对象 **浅 ** **深 **
可变对象 **深 ** **深 **

集合对象的copy和mutableCopy

集合类对象是指NSArray、NSDictionary、NSSet ... 之类的对象。

集合&不可变

   NSArray* arr = @[ @[ @"a", @"b" ], [@[ @"c", @"d" ] mutableCopy], @"AA", [NSMutableString stringWithString:@"a"] ];
   NSArray *arrCy = [arr copy];
   NSMutableArray *arrMCy = [arr mutableCopy];
   //! 分别打印变量的 内存地址 及对象地址
   NSLog(@"   arr :%p  %p  [%p %p %p %p]", arr, &arr, arr[0], arr[1], arr[2], arr[3]);
   NSLog(@" arrCy :%p  %p  [%p %p %p %p]", arrCy, &arrCy, arrCy[0], arrCy[1], arrCy[2], arrCy[3]);
   NSLog(@"arrMCy :%p  %p  [%p %p %p %p]", arrMCy, &arrMCy, arrMCy[0], arrMCy[1], arrMCy[2], arrMCy[3]);

   //! 输出 
       arr :0x100403430  0x7fff5fbff7a0  [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]
     arrCy :0x100403430  0x7fff5fbff798  [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]
    arrMCy :0x1004034f0  0x7fff5fbff790  [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]

可见

  • 不可变集合的copy操作是浅拷贝
  • 不可变集合的mutableCopy操作是单层深拷贝拷贝
  • 集合内部的元素仍然是浅拷贝,无论该元素是否是集合是否可变

下面在测试一下可变集合的拷贝实验

集合&可变

  NSMutableArray* arr = [NSMutableArray arrayWithObjects:@[ @"a", @"b" ], [NSMutableArray arrayWithObject:@"A"], @"AA", [NSMutableString stringWithString:@"a"], nil];
  NSArray* arrCy = [arr copy];
  NSMutableArray* arrMCy = [arr mutableCopy];

  NSLog(@"   arr :%p  %p  [%p %p %p %p]", arr, &arr, arr[0], arr[1], arr[2], arr[3]);
  NSLog(@" arrCy :%p  %p  [%p %p %p %p]", arrCy, &arrCy, arrCy[0], arrCy[1], arrCy[2], arrCy[3]);
  NSLog(@"arrMCy :%p  %p  [%p %p %p %p]", arrMCy, &arrMCy, arrMCy[0], arrMCy[1], arrMCy[2], arrMCy[3]);

    arr :0x1005023d0  0x7fff5fbff7d0  [0x1005000d0 0x100502140 0x1000010b8 0x100502320]
  arrCy :0x100502500  0x7fff5fbff7c8  [0x1005000d0 0x100502140 0x1000010b8 0x100502320]
 arrMCy :0x1005025c0  0x7fff5fbff7c0  [0x1005000d0 0x100502140 0x1000010b8 0x100502320]

可以看出

  • 可变集合的copy和mutableCopy都是单层深拷贝

集合拷贝结论

集合 copy mutableCopy
不可变对象 **浅 ** 单层深
可变对象 **单层深 ** **单层深 **

自定义对象的拷贝

浅拷贝

首先创建Person.h和Person.m, 实现<NSCopying>协议

#Person.h
@interface Person : NSObject <NSCopying>
@property (nonatomic,copy) NSString *name;
@end
#Person.m
@implementation Person
@synthesize name;
//实现copyWithZone方法
- (id)copyWithZone:(NSZone *)zone {
    Person *p = [[self class] allocWithZone:zone];
    p.name = [self name];
    return p;
}
@end
        Person* person = [[Person alloc] init];
        [person setName:@"leo"];
        NSArray* arr1 = [[NSArray alloc] initWithObjects:person, @"AA", @[ @"AA" ], [NSMutableArray arrayWithObjects:@"AA", nil], nil];
        NSArray* arr2 = [[NSArray alloc] initWithArray:arr1];
        NSArray* arr3 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
        [person setName:@"lily"];
        //尝试更改name的值
        //获取两个数组里的各自Person对象
        Person* p1 = [arr1 objectAtIndex:0];
        Person* p2 = [arr2 objectAtIndex:0];
        Person* p3 = [arr3 objectAtIndex:0];
        NSLog(@"arr1 :%p  非集合 :%p  不可变集合 :%p  可变集合 :%p", arr1, arr1[1], arr1[2], arr1[3]);
        NSLog(@"arr2 :%p  非集合 :%p  不可变集合 :%p  可变集合 :%p", arr2, arr2[1], arr2[2], arr2[3]);
        NSLog(@"arr3 :%p  非集合 :%p  不可变集合 :%p  可变集合 :%p", arr3, arr3[1], arr3[2], arr3[3]);

        NSLog(@"p1:%p  name:%@", p1, p1.name);
        NSLog(@"p2:%p  name:%@", p2, p2.name);
        NSLog(@"p3:%p  name:%@", p3, p3.name);

        arr1 :0x100404520  非集合 :0x100002090  不可变集合 :0x1004040a0  可变集合 :0x100404230
        arr2 :0x100404630  非集合 :0x100002090  不可变集合 :0x1004040a0  可变集合 :0x100404230
        arr3 :0x100404790  非集合 :0x100002090  不可变集合 :0x1004040a0  可变集合 :0x100404780
         p1:0x1004006b0  name:lily
         p2:0x1004006b0  name:lily
         p3:0x100404090  name:leo

其他

1、深浅copy对引用计数的影响

浅copy,类似strong,持有原始对象的指针,会使retainCount加一。
深copy,会创建一个新的对象,不会对原始对象的retainCount变化。

// 也许你会疑问arc下如何访问retainCount属性,这里提供了两种方式(下面代码中a代表一个任意对象,这个对象最好不要是NSString和NSNumber,因为用它们进行测试会出问题)
// kvc方式
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)a));
// 桥接字方式
NSLog(@"Retain count %@", [a valueForKey:@"retainCount"]);

3、可变和不可变

可变和不可变上文谈的不是很多,因为本文认为这完全与NSCopying和NSMutableCopying的实现息息相关。当然,对于框架类,我们可以简单的认为,copy方法返回的就是不可变对象,mutableCopy返回的就是可变对象。如果是自定义的类,就看你怎么实现NSCopying和NSMutableCopying协议了。

4、copy和block

首先,MRR时代用retain修饰block会产生崩溃,因为作为属性的block在初始化时是被存放在栈区或静态区的,如果栈区的block内调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一旦出了block的初始化作用域,就会引起崩溃。所有MRC中使用copy修饰,将block拷贝到堆上。
其次,在ARC时代,因为ARC自动完成了对block的copy,所以修饰block用copy和strong都无所谓。

5、strong和shallowCopy

这个问题困惑了很久,最后只能得出一个结论,浅copy和strong引用的区别仅仅是浅copy多执行一步copyWithZone:方法。

声明:本文非原创,仅仅整理一些开发技能知识文章,以作存档学习用
参考
http://www.jianshu.com/p/eb1b732b737d
http://www.jianshu.com/p/fd938ff32579

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

推荐阅读更多精彩内容

  • 1、对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接...
    滴答大阅读 741评论 0 2
  • 概念 在Objective-C中并不是所有的对象都支持Copy,MutableCopy,遵守NSCopying协议...
    LeoAu阅读 8,478评论 10 28
  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 823评论 0 2
  • 前言 不敢说覆盖OC中所有copy的知识点,但最起码是目前最全的最新的一篇关于 copy的技术文档了。后续发现有新...
    zyydeveloper阅读 3,244评论 4 35
  • ——《人类简史》小记 “佛教认为,快乐既不是主观感受到愉悦,也不是主观觉得生命有意义,反而是在于放下追求主观...
    567fa891c224阅读 238评论 0 1