在OC语法中,提供了Copy语法(Copy/MutableCopy)用于对象的拷贝。其中很容易混淆的是浅拷贝和深拷贝。所谓浅拷贝,即单纯的地址拷贝,并不产生新的对象,而是对原对象的引用计数值加1;而深拷贝,即是对象拷贝,产生新的对象副本,拥有独立的内存空间,引用计数器为1。
我们通过一张图先来看看深拷贝与浅拷贝的区别:
我们常用的有NSString/NSMutableString、NSArray/NSMutableArray、NSDictionary/NSMutableDictionary、NSSet、NSMutableSet等系统类对象,下面我们来验证什么情况下是深拷贝,什么情况下是浅拷贝。
先来测试一下NSString与NSMutableString:
NSString *str1 = @"str1"; NSMutableString *str2 = [str1 mutableCopy]; NSLog(@"str1=%p str2=%p", str1, str2); NSMutableString *str3 = [[NSMutableString alloc] initWithString:@"sdfsdf"];; NSMutableString *str4 = [str1 mutableCopy]; NSLog(@"str3=%p str4=%p", str3, str4); // 打印结果: // str1=0x10516e068 str2=0x7fdd73425bd0 // str3=0x7fdd7373d360 str4=0x7fdd7373a190
从打印结果可以看出来,对于NSString与NSMutableString的mutableCopy:
NSString类型对象调用mutableCopy得到的新对象,拥有独立的内存空间,因此是深拷贝。NSMutableString类型对象调用mutableCopy得到的新对象,拥有独立的内存空间,因此是深拷贝
测试NSArray/NSMutableArray的情况如何:
NSArray *array = @[@"array", @"array1"]; NSMutableArray *array1 = [array mutableCopy]; NSLog(@"array=%p, array1=%p", array, array1); for (id obj in array) { NSLog(@"%p", obj); } for (id obj in array1) { NSLog(@"%p", obj); } // 打印结果: array=0x7fdf196055c0, array1=0x7fdf19695fa0 0x105a66088 0x105a660a8 0x105a66088 0x105a660a8
由此可以知道,NSArray对象调用mutableCopy会重新新的可变数组对象,但是可变数组对象的元素并没有独立的内存空间,只是地址的复制而已,因此只是对数组进行了深拷贝,而对数组元素却是进行浅拷贝。这种情况叫做集合的单层深复制 (One-Level-Deep Copy)。
测试NSDictionary/NSMutableDictionary:
NSDictionary *dict = @{@"key" : @"value", @"key1" : @"value1"}; NSMutableDictionary *dict1 = [dict mutableCopy]; NSLog(@"dict=%p, dict1=%p", dict, dict1); for (id obj in dict.allValues) { NSLog(@"%p", obj); } for (id obj in dict1.allValues) { NSLog(@"%p", obj); } // 打印结果: dict=0x7f9e70c3a0b0, dict1=0x7f9e70c2f100 0x1021f40a0 0x1021f40e0 0x1021f40a0 0x1021f40e0
从打印结果可以看出来这是NSArray/NSMutableArray的情况是一样的,因此对于NSSet/NSMutableSet自然也是一样的,它们都属于集合类型。
我们来考虑NSString/NSMutableString之间的互相拷贝情况:
NSString对象copy得到NSString对象
NSMutableString对象copy得到NSString对象
NSString对象copy得到NSString对象:
NSString *str = @"标哥的技术博客"; // 因为NSString对象本身就不可变,所以并没产生新的对象,而是返回对象本身,但是引用计数加1 NSString *str2 = [str copy]; // 输出二者的地址,二者的地址是相同的 NSLog(@"str --> %p",str); NSLog(@"str2 --> %p",str2); // 打印结果: str --> 0x10c820068 str2 --> 0x10c820068
从打印结果可以看到地址是一样的,因此它并没有做深拷贝,而是浅拷贝。
NSMutableString对象copy得到NSString对象:
NSMutableString *str = [[NSMutableString alloc] initWithString:@"标哥的技术博客"]; NSString *str2 = [str copy]; NSLog(@"str --> %p",str); NSLog(@"str2 --> %p",str2); // 打印结果: str --> 0x7f9393e0fa90 str2 --> 0x7f9393e7cc10
从打印结果可以看到两个地址不一样,说明发生的是深拷贝。
对于自定义的对象,默认是不遵守NSCopying/NSMutableCopying协议的,如果我们希望自定义对象可以调用copy/mutableCopy,则需要手动去实现。
@interface Blog : NSObject<NSCopying> @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *version; @end @implementation Blog // 重写它 - (id)copyWithZone:(NSZone *)zone { Blog *blog = [[Blog alloc] init]; blog.name = self.name; blog.version = self.version; return blog; } @end
测试来测试一下是否可以正常调用copy:
Blog *blog = [[Blog alloc] init]; blog.name = @"标哥的技术博客"; blog.version = @"v1.0.0"; Blog *anotherBlog = [blog copy]; NSLog(@"blog=%p, anotherBlog=%p", blog, anotherBlog); // blog=0x7ffd13499f50, anotherBlog=0x7ffd1348f3a0
可以正常使用了!调用copy是生成新的对象!
似乎没有见过自定义的遵守NSMutableCopying协议的类型,因为对于可变操作通常是可以执行增、删、改、查操作的类型,最常见的自然是集合类型,还有字符串类型,所以对于自定义类型没有想出在哪些场景需要使用到,故暂不给出例子!
还有另外一篇是iOS深拷贝与浅拷贝精讲,也是对深拷贝与浅拷贝之间的区别的探索性文章,推荐给大家!
承接:ThinkPHP项目开发、网站项目开发、微信项目开发、微信小程序项目开发、App开发,欢迎联系标哥QQ632840804