iOS-浅拷贝和深拷贝

深拷贝和浅拷贝这个问题在面试中常常被问到,而在实际开发中,只要稍有不慎,就会在这里出现问题。尤其对于初学者来说,我们有必要来好好研究下这个概念。
首先通过一句话来解释:深拷贝就是内容拷贝,浅拷贝就是指针拷贝。
----深拷贝就是拷贝出和原来仅仅是值一样,但是内存地址完全不一样的新的对象,创建后和原对象没有任何关系。
----浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。
(1)非容器类对象的深拷贝、浅拷贝

// 字符串是直接赋值的,右侧如果是copy,那么就是浅拷贝;右侧如果是mutableCopy,那么就是深拷贝。  
   NSString *string1 = @"helloworld";  
   NSString *string2 = [string1 copy]; // 浅拷贝  
   NSString *string3 = [string1 mutableCopy]; // 深拷贝  
   NSMutableString *string4 = [string1 copy]; // 浅拷贝  
   NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝  
  
   NSLog(@"string1 = %d;string2 = %d",string1,string2);  
   NSLog(@"string1 = %d;string3 = %d",string1,string3);  
   NSLog(@"string1 = %d;string4 = %d",string1,string4);  
   NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:


image.png

我这里用%d格式化打印出字符串对象的内存地址。我这里主要通过内存地址来判断是深拷贝还是浅拷贝,在该案例中,无论是深拷贝还是浅拷贝,字符串对象的值都是一样的,所以暂且就不打印值了,而只打印地址。这里的原字符串是直接赋值的,可以发现,右侧如果使用copy,那么打印出的内存地址是一样的,表示是浅拷贝,只拷贝出了指向原对象的指针,没有创建出新的对象。如果右侧使用mutableCopy,那么打印出的不一样的内存地址,表示创建了一个新的对象,是深拷贝。
简单说明一下什么是非容器类对象,像NSString,NSNumber这些不能包含其他对象的叫做非容器类。如NSArray和NSDictionary可以容纳其他对象的叫做容器类对象。
做一个小小的总结:
-- 浅拷贝类似retain,引用计数对象+1.创建一个指针;
-- 深拷贝是真正意义上的拷贝,是创建一个新对象。copy属性表示两个对象内容相同,新的对象retain为1,与原对象的引用计数无关,原对象没有改变。copy减少对象对上下文的依赖。
-- retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1.也就是说,retain是指针拷贝,copy是内容拷贝。
(2)改变字符串的创建方式,使用stringWithFormat创建字符串,而不是直接赋值

// 结果同上。右侧如果是copy,那么就是浅拷贝;右侧如果是mutableCopy,那么就是深拷贝。  
   NSString *string1 = [NSString stringWithFormat:@"helloworld"];  
   NSString *string2 = [string1 copy]; // 浅拷贝  
   NSString *string3 = [string1 mutableCopy]; // 深拷贝  
   NSMutableString *string4 = [string1 copy]; // 浅拷贝  
   NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝  
  
   NSLog(@"string1 = %d;string2 = %d",string1,string2);  
   NSLog(@"string1 = %d;string3 = %d",string1,string3);  
   NSLog(@"string1 = %d;string4 = %d",string1,string4);  
   NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:


image.png

结果同(1)。因为都是不可变字符串,创建方式并不影响拷贝方式。所以可以得出结论,对于字符串这类非容器类对象,copy是浅拷贝,mutable是深拷贝。
(3)以上测试的都是不可变字符串,这里来尝试下可变字符串

// 如果是一个MutableString,那么无论是copy,mutableCopy,都会创建一个新对象。  
   NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];  
   NSString *string2 = [string1 copy]; // 深拷贝  
   NSString *string3 = [string1 mutableCopy]; // 深拷贝  
   NSMutableString *string4 = [string1 copy]; // 深拷贝  
   NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝  
  
   NSLog(@"string1 = %d;string2 = %d",string1,string2);  
   NSLog(@"string1 = %d;string3 = %d",string1,string3);  
   NSLog(@"string1 = %d;string4 = %d",string1,string4);  
   NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:


image.png

可以看到,对于MutableString,右侧无论是copy还是mutableCopy,都会创建一个新对象,都是属于深拷贝。
现在我们对以上代码做一点修改:

// 如果是一个MutableString,那么无论是copy,mutableCopy,都会创建一个新对象。  
   NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];  
   NSString *string2 = [string1 copy]; // 深拷贝  
   NSString *string3 = [string1 mutableCopy]; // 深拷贝  
   NSMutableString *string4 = [string1 copy]; // 深拷贝  
   NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝  
  
   NSLog(@"string1 = %d;string2 = %d",string1,string2);  
   NSLog(@"string1 = %d;string3 = %d",string1,string3);  
   NSLog(@"string1 = %d;string4 = %d",string1,string4);  
   NSLog(@"string1 = %d;string5 = %d",string1,string5);  
  
   // 增加以下代码  
   [string4 appendString:@"MMMMMM"];  
   [string5 appendString:@"11111"];  
  
   NSLog(@"string4 = %@",string4);  
   NSLog(@"string5 = %@",string5); 

我增加了四行代码,我想要检验一下对一个可变字符串执行copy和mutableCopy后,返回的字符串是否可变。同时为了检验在哪一行出现crash,我提前打一个异常断点。对于NSMutableString有appendString方法,而NSString没有appendString方法,所以这种检验应该是没有问题的。运行代码后,会在[string4 apendString:@“MMMMM”];这一行出现崩溃。经过实际测试,可得出以下结论:
-- 对于可变对象的复制,都是深拷贝;
-- 可变对象copy后返回的对象是不可变的,mutableCopy后返回的对象是可变的。
(4)我们上面提到的都是系统类,如果是我们自定义的类,复制结果又是怎样呢?我下面定义一个Person类,实现如下:
Person.h

#import <Foundation/Foundation.h>  
  
@interface Person : NSObject  
  
@property (nonatomic, copy) NSString *name;  
@property (nonatomic, assign) NSInteger age;  
  
@end  

Person.m

#import "Person.h"  
  
@interface Person()<NSCopying, NSMutableCopying>  
  
@end  
  
@implementation Person  
  
// 对应copy方法  
- (id)copyWithZone:(NSZone *)zone  
{  
    Person *person = [[Person allocWithZone:zone] init];  
    person.name = self.name; // 这里的self就是被copy的对象  
    person.age = self.age;  
    return person;  
}  
  
- (id)mutableCopyWithZone:(NSZone *)zone  
{  
    Person *person = [[Person allocWithZone:zone] init];  
    person.name = self.name;  
    person.age = self.age;  
    return person;  
}  
  
  
@end  

首先声明一下为什么要实现上述的copyWithZone和mutableCopyWithZone方法?其实在iOS中并不是所有的对象都支持copy、mutableCopy方法,只有遵守NSCopying协议的类可以发送copy消息,遵守NSMutableCopying协议的类才可以发送mutableCopy消息,否则就会崩溃。我们可以非常方便的使用系统类中的copy,mutableCopy方法,是因为这些系统类本身就实现了NSCopying,NSMutableCopy协议,大家可以进入接口文档进行查看。
然后编写测试代码如下:

// Person类必须实现copyWithZone和mutableCopyWithZone方法。  
    Person *person = [[Person alloc] init];  
    person.name = @"Jack";  
    person.age = 23;  
  
    Person *copyPerson = [person copy]; // 深拷贝  
    Person *mutableCopyPerson = [person mutableCopy]; // 深拷贝  
    NSLog(@"person = %d;copyPerson = %d",person,copyPerson);  
    NSLog(@"person = %d;mutableCopyPerson = %d",person,mutableCopyPerson); 

打印结果如下:


image.png

可以看到无论是用copy还是mutableCopy,复制后都创建了一个新的对象。为什么呢?因为我们本来就在copyWithZone: ,mutableCopyWithZone方法中创建了一个新对象啊,所以一点都不奇怪。
(5)从上述(4)中可以看到我的Person自定义对象在copy后是创建了一个全新的对象,那么这个对象中的属性呢?这些属性是深拷贝还是浅拷贝呢?

NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack"];  
   Person *person = [[Person alloc] init];  
   person.name = otherName;  
   person.age = 23;  
  
   [otherName appendString:@" and Mary"];  
   NSLog(@"person.name = %@",person.name); 

打印结果如下:


image.png

打印的结果是"Jack",而不是”Jack and Mary“. 说明在执行person.name = otherName;的时候是重新创建了一个字符串。但是请大家注意,这里我name的属性修饰符是copy,如果把copy改成strong会变成怎么样呢?
对,没错,name改为strong后答案就是”Jack and Mary“。所以在这里我们可以得出以下小小的结论:
-- 因为这里的otherName是可变的,person.name的属性是copy,所以创建了新的字符串,属于深拷贝;
-- 如果person.name设置为strong,那么就是浅拷贝。因为strong会持有原来的对象,使原来的对象的引用计数+1,其实就是指针拷贝。
-- 当然,这里用strong还是copy,取决于实际需求。
(6)与上面(5)类似,我现在修改代码如下:

NSString *otherName = @"jack";  
    Person *person = [[Person alloc] init];  
    person.name = otherName;  
    person.age = 23;  
  
    NSLog(@"othername = %d;person.name = %d",otherName,person.name); 

其实这里很好理解,无论person.name是strong还是copy,都是指针拷贝。指向的都是otherName的内存地址。
现修改代码如下:

NSString *otherName = @"jack";  
   Person *person = [[Person alloc] init];  
   person.name = [otherName copy];  
   person.age = 23;  
  
   NSLog(@"othername = %d;person.name = %d",otherName,person.name); 

同样的,无论person.name是strong还是copy,都是指针拷贝。指向的都是otherName的内存地址。因为对不可变对象执行copy一般都是浅拷贝,这没问题。
再次修改代码如下:

NSString *otherName = @"jack";  
   Person *person = [[Person alloc] init];  
   person.name = [otherName mutableCopy];  
   person.age = 23;  
  
   NSLog(@"othername = %d;person.name = %d",otherName,person.name);

无论person.name是strong还是copy,都是内容拷贝。创建一个全新的对象。因为对不可变对象执行mutableCopy一般都是深拷贝,也没问题。
(7)讲了这么多非容器对象,最后我们来讲讲容器对象Array。先讲不可变的容器对象

NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil nil];  
   NSArray *copyArray01 = [array01 copy];  
   NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];  
   NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);  
   NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);  
  
  
   NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);  
   NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);  
   NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);
image.png

我们可以得出以下结论:
-- copyArray01和array01指向的是同一个对象,包括里面的元素也是指向相同的指针。
-- mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。
-- copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。
---------------------------------------------------------------分割线-------------------------------------------------------
上面的是不可变容器NSArray,这里测试下可变容器NSMutableArray:

NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil nil];  
    NSArray *copyArray01 = [array01 copy];  
    NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];  
    NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);  
    NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);  
    
    NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);  
    NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);  
    NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]); 

打印的结果如下:


image.png

可得结论如下:
-- 容器对象和非容器对象类似,可变对象复制(copy,mutableCopy)的都是一个新对象;不可变对象copy是浅复制,mutableCopy是深复制。
-- 对于容器而言,元素对象始终是指针复制。

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

推荐阅读更多精彩内容