前言
关于iOS中对象的深拷贝和浅拷贝的文章有很多,但是大部分都是基于打印内存地址来推导结果,这篇文章是从源码的角度来分析深拷贝和浅拷贝。
深拷贝和浅拷贝的概念
拷贝的方式有两种:深拷贝和浅拷贝。
浅拷贝就是拷贝后,并没有进行真正的复制,而是复制的对象和原对象都指向同一个地址
深拷贝是真正的复制了一份,复制的对象指向了新的地址
从上图可以看出,浅拷贝A指针改变了所指向的内容B指针也指向被修改后的内容。如果有些地方用到B指针,不希望在A指向的内容发生变化时也跟着变化,则需要用到深拷贝。
通俗理解为:浅拷贝好比你的影子,你死了,影子也没了;深拷贝好比克隆人,你死了,它还在。
对象的copy和mutableCopy方法
不管是集合对象还是非集合对象,接收到copy和mutableCopy消息时,都遵循以下准则:
下面对非集合对象和集合对象的copy和mutableCopy方法进行具体的阐述。
1.非集合类对象的copy和mutableCopy方法
非集合类对象指的是NSString,NSNumber...这些类。下面的例子以NSString类为例。
首先来看immutable对象拷贝的例子:
NSString *string = @"test"; NSString *copyString = [string copy]; NSMutableString *mutableCopyString = [string mutableCopy]; NSLog(@"%p \n %p \n %p \n", string, copyString, mutableCopyString);
打印结果:
0x101545068
0x101545068
0x60000024e940
通过打印结果我们可以看出来,copyString和string的地址值一样,而mutableCopyString和string的地址值不一样,这就说明imutable对象的copy方法进行了浅拷贝,mutableCopy方法进行了深拷贝。
再来看看mutable对象拷贝的例子:
NSMutableString *string = [[NSMutableString alloc] initWithString:@"test"]; NSString *copyString = [string copy]; NSMutableString *mutableCopyString = [string mutableCopy]; NSLog(@"%p \n%p \n%p \n", string, copyString, mutableCopyString);
打印结果:
0x600000240e40
0xa000000747365744
0x6000002411a0
通过打印结果可以看出来,copyString和string的内存地址不同,mutableCopyString和string的内存地址也不同。这说明mutable对象的copy方法和mutableCopy方法都进行了深拷贝。
总结起来就是:
immutable对象的copy方法进行了浅拷贝
immutable对象的mutableCopy方法进行了深拷贝
mutable对象的copy方法进行了深拷贝
mutable对象的mutableCopy方法进行了深拷贝。
用代码表示就是:
[immutableObject copy];//浅拷贝 [immutableObject mutableCopy];//深拷贝 [mutableObject copy];//深拷贝 [mutableObject mutableCopy];//深拷贝
以上是通过打印内存地址得出的结论,下面我们通过查看源码来证实一下我们的结论。
在opensource.apple.com的git仓库中的runtime源码中有NSObject.mm这个文件,在这个文件中有copy和mutableCopy方法的实现:
- (id)copy { return [(id)self copyWithZone:nil]; } - (id)mutableCopy { return [(id)self mutableCopyWithZone:nil]; }
我们发现copy和mutableCopy方法只是简单的调用了copyWithZone:和mutableCopyWithZone:两个方法。然后我在searchcode.com中找到了NSString和NSMutableString的Source Code。
在NSString.m中,找到了关于copy的方法:
- (id)copyWithZone:(NSZone *)zone { if (NSStringClass == Nil) NSStringClass = [NSString class]; return RETAIN(self); } - (id)mutableCopyWithZone:(NSZone*)zone { return [[NSMutableString allocWithZone:zone] initWithString:self]; }
通过这个源码我们知道了,对于NSString对象,调用copy方法就是调用了copyWithZone:方法。而copyWithZone:方法并没有创建新的对象,而是使指针持有了原来的NSString对象,所以NSString的copy方法是浅拷贝。
而调用mutableCopy方法就是调用了mutableCopyWithZone:方法。从mutableCopyWithZone:的实现我们可以看到,这个方法是创建了一个新的可变的字符串对象。因此NSString的mutableCopy方法是深拷贝。
在NSMutableString.m中,只找到了copy和copyWithZone:方法,并没有找到mutableCopyWithZone:方法:
-(id)copy { return [[NSString alloc] initWithString:self]; } -(id)copyWithZone:(NSZone*)zone { return [[NSString allocWithZone:zone] initWithString:self]; }
对NSMutableString对象调用copy方法会调用这里的copyWithZone:方法的实现,我们可以看到这里创建了一个新的不可变的字符串。所以对NSMutableString对象执行copy方法是深拷贝。
由于在NSMutableString中没有实现mutableCopyWithZone:方法,所以会调用父类的mutableCopyWithZone:方法,也就是NSString类的mutableCopyWithZone:方法,而我们知道,NSString类的mutableCopyWithZone:方法会创建一个新的可变字符串。所以对NSMutableString对象执行mutableCopy方法是深拷贝。
2.集合对象的copy和mutableCopy
集合对象指的是NSArray,NSDictionary,NSSet等之类的对象。下面以NSArray为例看看immutable对象使用copy和mutableCopy的例子:
NSArray *array = @[@"1", @"2", @"3"]; NSArray *copyArray = [array copy]; NSMutableArray *mutableCopyArray = [array mutableCopy]; NSLog(@"%p\n%p\n%p", array, copyArray, mutableCopyArray);
打印结果:
0x60400025bed0
0x60400025bed0
0x60400025c2f0
通过打印结果可以看出来,copyArray的地址和array的地址是一样的,说明对array进行copy是进行浅拷贝。而
mutableCopyArray的地址和array的地址是不一样的,说明对array进行mutableCopy是进行了深拷贝。
再来看mutable对象执行copy和mutableCopy的例子:
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[@"1", @"2", @"3"]]; NSArray *copyArray = [array copy]; NSMutableArray *mutableCopyArray = [array mutableCopy]; NSLog(@"%p\n%p\n%p", array, copyArray, mutableCopyArray);
打印结果:
0x604000447440
0x604000447050
0x604000447080
通过打印结果可以看出,copyArray和mutableCopyArray的地址都和array的地址不同,这说明对可变数组进行copy和mutableCopy操作都进行了深拷贝。
因此得出结论:
在集合类对象中,对immutable对象进行copy操作是浅拷贝,进行mutableCopy操作是深拷贝。对mutable对象进行copy操作是深拷贝,进行mutableCopy操作是深拷贝。
用代码表示就是:
[immutableObject copy];//浅拷贝 [immutableObject mutableCopy];//深拷贝 [mutableObject copy];//深拷贝 [mutableObject mutableCopy];//深拷贝
以上是通过打印内存地址得到的结论,下面我们通过源码来验证一下我们的结论。
在NSArray.m中,我找到了copyWithZone:和mutableCopyWithZone:方法。
- (id)copyWithZone:(NSZone *)zone { return RETAIN(self); } - (id)mutableCopyWithZone:(NSZone*)zone { if (NSMutableArrayClass == Nil) NSMutableArrayClass = [NSMutableArray class]; return [[NSMutableArrayClass alloc] initWithArray:self]; }
当调用copy方法时,实际上是执行了这里的copyWithZone:方法,在这个方法里面并没有创建新的对象,而只是持有了旧的对象,因此,对于不可变的数组对象,执行copy操作是浅拷贝。
当调用mutableCopy方法时,实际上是执行了这里的mutableCopyWithZone:方法,在这个方法里面,利用原来的数组对象,创建了一个新的可变数组对象,因此对于不可变的数组对象,执行mutableCopy操作是深拷贝。
在NSArray.m这个文件的第825行是NSMutableArray的实现。在第875行找到了copyWithZone:的实现,没有找到mutableCopyWithZone:的实现:
- (id)copyWithZone:(NSZone*)zone { if (NSArrayClass == Nil) NSArrayClass = [NSArray class]; return [[NSArrayClass alloc] initWithArray:self copyItems:YES]; }
当调用copy方法时,实际是调用了这里的copyWithZone:方法,在这个方法的实现里,是利用原来的可变数组创建了一个新的不可变数组,因此对可变数组执行copy操作是深拷贝。
当调用mutableCopy时,由于NSMutableArray本身没有实现mutableCopyWithZone:方法,所以会调用父类也就是NSArray类的实现,而通过上面我们也能看到NSArray的实现:利用原数组创建了一个新的可变数组,因此,对可变数组进行mutableCopy操作是深拷贝。
回答经典面试题
面试题:为什么NSString类型的成员变量的修饰属性用copy而不是strong呢?
首先要搞清楚的就是对NSString类型的成员变量用copy修饰和用strong修饰的区别。如果使用了copy修饰符,那么在给成员变量赋值的时候就会对被赋值的对象进行copy操作,然后再赋值给成员变量。如果使用的是strong修饰符,则不会执行copy操作,直接将被赋值的变量赋值给成员变量。
假设有一个NSString类型的成员变量string,对其进行赋值:
NSString *testString = @"test"; self.string = testString;
如果该成员变量是用copy修饰的,则等价于:
self.string = [testString copy];
如果是用strong修饰的,则没有copy操作:
self.string = testString;
知道了使用copy和strong的区别后,我们再来分析为什么要使用copy修饰符。先看一段代码:
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"test"]; self.string = mutableString; NSLog(@"%@", self.string); [mutableString appendString:@"addstring"]; NSLog(@"%@", self.string);
如果这里成员变量string是用strong修饰的话,打印结果就是:
2018-09-04 10:50:16.909998+0800 copytest[2856:78171] test
2018-09-04 10:50:16.910128+0800 copytest[2856:78171] testaddstring
很显然,当mutableString的值发生了改变后,string的值也随之发生改变,因为self.string = mutableString;这行代码实际上是执行了一次指针拷贝。string的值随mutableString的值的发生改变这显然不是我们想要的结果。
如果成员变量string是用copy修饰,打印结果就是:
2018-09-04 10:58:07.705373+0800 copytest[3024:84066] test
2018-09-04 10:58:07.705496+0800 copytest[3024:84066] test
这是因为使用copy修饰符后,self.string = mutableString;就等价于self.string = [mutableString copy];,也就是进行了一次深拷贝,所以mutableString的值再发生变化就不会影响到string的值。
回答面试题:
NSString类型的成员变量使用copy修饰而不是strong修饰是因为有时候赋给该成员变量的值是NSMutableString类型的,这时候如果修饰符是strong,那成员变量的值就会随着被赋值对象的值的变化而变化。若是用copy修饰,则对NSMutableString类型的值进行了一次深拷贝,成员变量的值就不会随着被赋值对象的值的改变而改变。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。
本文向大家介绍iOS 深拷贝 和浅拷贝的区别相关面试题,主要包含被问及iOS 深拷贝 和浅拷贝的区别时的应答技巧和注意事项,需要的朋友参考一下 深拷贝和浅拷贝的区别? 答案:浅层复制:只复制指向对象的指针,而不复制引用对象本身。 深层复制:复制引用对象本身。 意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,
本文向大家介绍深拷贝与 浅拷贝的区别?相关面试题,主要包含被问及深拷贝与 浅拷贝的区别?时的应答技巧和注意事项,需要的朋友参考一下 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
主要内容:到底是浅拷贝还是深拷贝对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如: b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存, 这种默认的拷贝行为就是 浅拷贝 ,这和调用 memcpy() 函数的效果非常类似。 对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一
浅拷贝 对于对象或数组类型,当我们将a赋值给b,然后更改b中的属性,a也会随着变化。 也就是说,a和b指向了同一块堆内存,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝。 深拷贝 那么相应的,如果给b放到新的内存中,将a的各个属性都复制到新内存里,就是深拷贝。 也就是说,当b中的属性有变化的时候,a内的属性不会发生变化。 参考链接: 深拷贝与浅拷贝的实现(一) javaScript中浅拷
一、引言 对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。 二、浅拷贝 1、什么是浅拷贝 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着
本文向大家介绍Python直接赋值、浅拷贝与深度拷贝实例分析,包括了Python直接赋值、浅拷贝与深度拷贝实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python直接赋值、浅拷贝与深度拷贝。分享给大家供大家参考,具体如下: 直接赋值:其实就是对象的引用(别名)。 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。 深拷贝(deepcopy): copy 模块的 dee