iOS开发——深拷贝与浅拷贝详解
来源:程序员人生 发布时间:2016-08-24 08:28:08 阅读次数:2357次
深拷贝和浅拷贝这个问题在面试中常常被问到,而在实际开发中,只要稍有不慎,就会在这里出现问题。特别对初学者来讲,我们有必要来好好研究下这个概念。我会以实际代码来演示,相干示例代码上传至 这里 。
首先通过1句话来解释:深拷贝就是内容拷贝,浅拷贝就是指针拷贝。
深拷贝就是拷贝出和原来仅仅是值1样,但是内存地址完全不1样的新的对象,创建后和原对象没有任何关系。浅拷贝就是拷贝指向原来对象的指针,使原对象的援用计数+1,可以理解为创建了1个指向原对象的新指针而已,并没有创建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);
打印结果以下:
。
我这里用%d格式化打印出字符串对象的内存地址。我这里主要通过内存地址来判断是深拷贝还是浅拷贝,在该案例中,不管是深拷贝还是浅拷贝,字符串对象的值都是1样的,所以暂且就不打印值了,而只打印地址。这里的原字符串是直接赋值的,可以发现,右边如果使用copy,那末打印出的内存地址是1样的,表示是浅拷贝,只拷贝出了指向原对象的指针,没有创建出新的对象。如果右边使用mutableCopy,那末打印出的不1样的内存地址,表示创建了1个新的对象,是深拷贝。
简单说明1下甚么是非容器类对象,像NSString,NSNumber这些不能包括其他对象的叫做非容器类。如NSArray和NSDictionary可以容纳其他对象的叫做容器类对象。
做1个小小的总结:
-- 浅拷贝类似retain,援用计数对象+1.创建1个指针;
-- 深拷贝是真正意义上的拷贝,是创建1个新对象。copy属性表示两个对象内容相同,新的对象retain为1,与原对象的援用计数无关,原对象没有改变。copy减少对象对上下文的依赖。
-- retain属性表示两个对象地址相同(建立1个指针,指针拷贝),内容固然相同,这个对象的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);
打印结果以下:
。
结果同(1)。由于都是不可变字符串,创建方式其实不影响拷贝方式。所以可以得出结论,对字符串这类非容器类对象,copy是浅拷贝,mutable是深拷贝。
(3)以上测试的都是不可变字符串,这里来尝试下可变字符串
// 如果是1个MutableString,那末不管是copy,mutableCopy,都会创建1个新对象。
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);
打印结果以下:
。
可以看到,对MutableString,右边不管是copy还是mutableCopy,都会创建1个新对象,都是属于深拷贝。
现在我们对以上代码做1点修改:
// 如果是1个MutableString,那末不管是copy,mutableCopy,都会创建1个新对象。
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);
我增加了4行代码,我想要检验1下对1个可变字符串履行copy和mutableCopy后,返回的字符串是不是可变。同时为了检验在哪1行出现crash,我提早打1个异常断点。对NSMutableString有appendString方法,而NSString没有appendString方法,所以这类检验应当是没有问题的。运行代码后,会在[string4 apendString:@“MMMMM”];这1行出现崩溃。经过实际测试,可得出以下结论:
-- 对可变对象的复制,都是深拷贝;
-- 可变对象copy后返回的对象是不可变的,mutableCopy后返回的对象是可变的。
(4)我们上面提到的都是系统类,如果是我们自定义的类,复制结果又是怎样呢?我下面定义1个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
首先声明1下为何要实现上述的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);
打印结果以下:
。
可以看到不管是用copy还是mutableCopy,复制后都创建了1个新的对象。为何呢?由于我们本来就在copyWithZone: ,mutableCopyWithZone方法中创建了1个新对象啊,所以1点都不奇怪。
(5)从上述(4)中可以看到我的Person自定义对象在copy后是创建了1个全新的对象,那末这个对象中的属性呢?这些属性是深拷贝还是浅拷贝呢?
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);
打印结果以下:
。
打印的结果是"Jack",而不是”Jack and Mary“. 说明在履行person.name = otherName;的时候是重新创建了1个字符串。但是请大家注意,这里我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的内存地址。由于对不可变对象履行copy1般都是浅拷贝,这没问题。
再次修改代码以下:
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,都是内容拷贝。创建1个全新的对象。由于对不可变对象履行mutableCopy1般都是深拷贝,也没问题。
(7)讲了这么多非容器对象,最后我们来说讲容器对象Array。先讲不可变的容器对象
NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", 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]);
打印结果以下:
。
我们可以得出以下结论:
-- copyArray01和array01指向的是同1个对象,包括里面的元素也是指向相同的指针。
-- mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。
-- copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------打个分割线
上面的是不可变容器NSArray,这里测试下可变容器NSMutableArray:
NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", 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]);
打印的结果以下:
。
可得结论以下:
-- 容器对象和非容器对象类似,可变对象复制(copy,mutableCopy)的都是1个新对象;不可变对象copy是浅复制,mutableCopy是深复制。
-- 对容器而言,元素对象始终是指针复制。
以上触及的也只是1部份,想要对深拷贝、浅拷贝有更加深入的了解,必须首先要对内存管理较为熟习,然后在实际开发中不断去实践,才会为所欲为。
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠