In object-oriented programming, object copying is creating a copy of an existing object, a unit of data in object-oriented programming. The resulting object is called an object copy or simply copy of the original object. Copying is basic but has subtleties and can have significant overhead. There are several ways to copy an object, most commonly by a copy constructor or cloning. Copying is done mostly so the copy can be modified or moved, or the current value preserved. If either of these is unneeded, a reference to the original data is sufficient and more efficient, as no copying occurs.html
在面向对象的程序设计中,对象的copy就是建立一个已经存在的对象的copy。这种对象的建立的结果被称为原始对象的copy。copy是很基础的,可是也有其精巧的地方,而且可能形成巨大的消耗。有不少种方式能够copy对象,最经常使用的就是copy构造器和克隆。copy常常用于对象的修改、移动和保护。若是上述的几种应用都不须要,持有原始对象的引用就足够了,并不须要copy。安全
In Objective-C, the methods copy and mutableCopy are inherited by all objects and intended for performing copies; the latter is for creating a mutable type of the original object. These methods in turn call the copyWithZone and mutableCopyWithZone methods, respectively, to perform the copying. An object must implement the corresponding copyWithZone method to be copyable.app
在OC中,copy和mutableCopy两个方法是被全部对象继承的(有点小毛病,应该指全部继承自NSObject的类),这两个方法就是为copy准备的。其中,mutableCopy是为了建立原始对象的可变类型的copy。这两个方法分别调用copyWithZone和mutableCopyWithZone两个方法来进行copy。一个对象必须实现copyWithZone或者mutableCopyWithZone,才能进行copy或者mutableCopy。框架
那么,咱们能够从以上获取到什么信息?测试
下一阶段,本文将展开讲述OC中的copy相关信息以及如何使用copy方法。ui
@protocol NSCopying - (id)copyWithZone:(nullable NSZone *)zone; @end @protocol NSMutableCopying - (id)mutableCopyWithZone:(nullable NSZone *)zone; @end
- (id)copy; - (id)mutableCopy;
首先,咱们谈一下非容器对象的深浅copy,这些非容器对象,包含经常使用的NSString、NSNumber等,也包括咱们自定义的一些非容器类的实例。下面分三个三面进行分析。atom
准则
浅copy:指针复制,不会建立一个新的对象。
深copy:内容复制,会建立一个新的对象。spa
此处,不进行过多的解释,从下面的结果分析中,按例子来理解。设计
准则
探究框架类深copy仍是浅copy,须要清楚的是该类如何实现的NSCopying和NSMutableCopy的两个方法copyWithZone:和mutableCopyWithZone:。然而OC并不开源,而且本文这里也不会进行源码的推测。
那么,咱们应该遵循怎样一个原则呢?以下:指针
代码
// 此处以NSString为例探究框架类深浅copy // 不可变对象 NSString *str = @"1"; NSString *str1 = [str copy]; NSString *str2 = [str mutableCopy]; // 可变对象 NSMutableString *mutableStr = [NSMutableString stringWithString:@"1"]; NSMutableString *mutableStr1 = [mutableStr copy]; NSMutableString *mutableStr2 = [mutableStr mutableCopy]; // 打印对象的指针来确认是否建立了一个新的对象 // 不可变对象原始指针 NSLog(@"%p", str); // 不可变对象copy后指针 NSLog(@"%p", str1); // 不可变对象mutalbeCopy后指针 NSLog(@"%p", str2); // 可变对象原始指针 NSLog(@"%p", mutableStr); // 可变对象copy后指针 NSLog(@"%p", mutableStr1); // 可变对象mutalbeCopy后指针 NSLog(@"%p", mutableStr2);
结果分析
// 此处依次对应上述6个log,可见与前面所讲的原则吻合(此处不验证可变类型和不可变类型,默认上述原则正确便可)。 2016-10-21 10:50:52.879 Memory[67680:5623387] 0x10d85a1b0 2016-10-21 10:50:52.879 Memory[67680:5623387] 0x10d85a1b0 2016-10-21 10:50:52.879 Memory[67680:5623387] 0x60800007a080 2016-10-21 10:50:52.879 Memory[67680:5623387] 0x60800007a9c0 2016-10-21 10:50:52.880 Memory[67680:5623387] 0xa000000000000311 2016-10-21 10:50:52.880 Memory[67680:5623387] 0x60800007a900
准则
对于一个咱们自定义的类型,显然比框架类容易操纵的多。此处就拿NSCopying举例(由于从没有自定义过具备可变类型的类,固然,若是有须要的话,也能够实现NSMutableCopying)。自定义的类就和2中的原则没有半毛钱关系了,一切就看你怎么实现NSCopying协议中的copyWithZone:方法。
代码
// Model定义,copyWithZone第一种实现(浅copy) @interface Model1 : NSObject <NSCopying> @property (nonatomic, assign) NSInteger a; @end @implementation Model1 - (id)copyWithZone:(NSZone *)zone { return self; } @end // Model定义,copyWithZone第二种实现(深copy) @interface Model1 : NSObject <NSCopying> @property (nonatomic, assign) NSInteger a; @end @implementation Model1 - (id)copyWithZone:(NSZone *)zone { Model1 *model = [[Model1 allocWithZone:zone] init]; model.a = self.a; return model; } @end // 分别选择上述两种model进行指针打印。 Model1 *model = [[Model1 alloc] init]; Model1 *copyModel = [model copy]; NSLog(@"%p", model); NSLog(@"%p", copyModel);
结果分析
// 对应上述一,可见实现了浅copy 2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0 2016-10-21 11:12:03.149 Memory[67723:5636292] 0x60000000c9d0 // 对应上述二,可见实现了深copy 2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001df00 2016-10-21 11:16:46.803 Memory[67752:5640133] 0x60800001def0
前文已经知道了深浅copy的区别,你也大体猜到了为何将容器对象拿出来做为一块。对,由于容器中可能包含不少对象,而这些对象也须要区分深浅copy。往深里说,容器中可能包含容器对象,那更是麻烦了。不要急,看下面,以NSArray的深浅copy为例,将容器的深浅copy分为四种。
准则
容器的浅copy,符合三.2中的原则。
代码
// 和NSString浅copy的验证步骤同样 NSArray *arr = [NSArray arrayWithObjects:@"1", nil]; NSArray *copyArr = [arr copy]; NSLog(@"%p", arr); NSLog(@"%p", copyArr);
结果分析
// 无疑是浅copy(你可能会问,为何不看一下arr和copyArr内部元素的指针对比?这里并无必要,最外层对象都没有建立新的,里面不用验证) 2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690 2016-10-21 11:27:57.554 Memory[67778:5646253] 0x600000010690
准则
容器的单层深copy,符合三.2中的原则(只是深copy变成了单层深copy)。这里的单层指的是完成了NSArray对象的深copy,而未对其容器内对象进行处理。
代码
NSArray *arr = [NSArray arrayWithObjects:@"1", nil]; NSArray *copyArr = [arr mutableCopy]; NSLog(@"%p", arr); NSLog(@"%p", copyArr); // 打印arr、copyArr内部元素进行对比 NSLog(@"%p", arr[0]); NSLog(@"%p", copyArr[0]);
结果分析
// 可发现前两项地址不一样,即完成深copy,可是后两项相同,这表明容器内部的元素并无完成深copy,全部称之为单层深copy 2016-10-21 11:32:27.157 Memory[67801:5649757] 0x6000000030d0 2016-10-21 11:32:27.157 Memory[67801:5649757] 0x600000242e50 2016-10-21 11:32:27.157 Memory[67801:5649757] 0x10dd811b0 2016-10-21 11:32:27.157 Memory[67801:5649757] 0x10dd811b0
准则
容器的双层深copy已经脱离了三.2中的原则。这里的双层指的是完成了NSArray对象和NSArray容器内对象的深copy(为何不说彻底,是由于没法处理NSArray中还有一个NSArray这种状况)。
代码
// 随意建立一个NSMutableString对象 NSMutableString *mutableString = [NSMutableString stringWithString:@"1"]; // 随意建立一个包涵NSMutableString的NSMutableArray对象 NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"]; NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil]; // 将mutableString和mutableArr放入一个新的NSArray中 NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil]; // 经过官方文档提供的方式建立copy NSArray *testArrCopy = [[NSArray alloc] initWithArray:testArr copyItems:YES]; // testArr和testArrCopy指针对比 NSLog(@"%p", testArr); NSLog(@"%p", testArrCopy); // testArr和testArrCopy中元素指针对比 // mutableString对比 NSLog(@"%p", testArr[0]); NSLog(@"%p", testArrCopy[0]); // mutableArr对比 NSLog(@"%p", testArr[1]); NSLog(@"%p", testArrCopy[1]); // mutableArr中的元素对比,即mutalbeString1对比 NSLog(@"%p", testArr[1][0]); NSLog(@"%p", testArrCopy[1][0]);
结果分析
// 这里能够发现,copy后,只有mutableArr中的mutalbeString1指针地址没有变化。而testArr的指针和testArr中的mutableArr、mutableString的指针地址均发生变化。因此称之为双层深复制。 2016-10-21 12:03:15.549 Memory[67855:5668888] 0x60800003c7a0 2016-10-21 12:03:15.549 Memory[67855:5668888] 0x60800003c880 2016-10-21 12:03:15.549 Memory[67855:5668888] 0x608000260540 2016-10-21 12:03:15.550 Memory[67855:5668888] 0xa000000000000311 2016-10-21 12:03:15.550 Memory[67855:5668888] 0x60800005d610 2016-10-21 12:03:15.550 Memory[67855:5668888] 0x60800000d2e0 2016-10-21 12:03:15.550 Memory[67855:5668888] 0x608000260980 2016-10-21 12:03:15.550 Memory[67855:5668888] 0x608000260980
限制
initWithArray: copyItems:会使NSArray中元素均执行copy方法。这也是我在testArr中放入NSMutableArray和NSMutableString的缘由。若是我放入的是NSArray或者NSString,执行copy后,只会发生指针复制;若是我放入的是未实现NSCopying协议的对象,调用这个方法甚至会crash。这里,官方文档的描述有误。
If the objects in the collection have adopted the NSCopying
protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects.
准则
若是想完美的解决NSArray嵌套NSArray这种情形,可使用归档、解档的方式。
代码
// 随意建立一个NSMutableString对象 NSMutableString *mutableString = [NSMutableString stringWithString:@"1"]; // 随意建立一个包涵NSMutableString的NSMutableArray对象 NSMutableString *mutalbeString1 = [NSMutableString stringWithString:@"1"]; NSMutableArray *mutableArr = [NSMutableArray arrayWithObjects:mutalbeString1, nil]; // 将mutableString和mutableArr放入一个新的NSArray中 NSArray *testArr = [NSArray arrayWithObjects:mutableString, mutableArr, nil]; // 经过归档、解档方式建立copy NSArray *testArrCopy = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:testArr]];; // testArr和testArrCopy指针对比 NSLog(@"%p", testArr); NSLog(@"%p", testArrCopy); // testArr和testArrCopy中元素指针对比 // mutableString对比 NSLog(@"%p", testArr[0]); NSLog(@"%p", testArrCopy[0]); // mutableArr对比 NSLog(@"%p", testArr[1]); NSLog(@"%p", testArrCopy[1]); // mutableArr中的元素对比,即mutalbeString1对比 NSLog(@"%p", testArr[1][0]); NSLog(@"%p", testArrCopy[1][0]);
结果分析
// 可见完成了彻底深复制,testArr和testArrCopy中的元素,以及容器中容器的指针地址彻底不一样,因此完成了彻底深复制。 2016-10-21 12:19:34.022 Memory[67887:5677318] 0x60800002db00 2016-10-21 12:19:34.022 Memory[67887:5677318] 0x60800002dc20 2016-10-21 12:19:34.022 Memory[67887:5677318] 0x608000260400 2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080002603c0 2016-10-21 12:19:34.023 Memory[67887:5677318] 0x608000051d90 2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080000521e0 2016-10-21 12:19:34.023 Memory[67887:5677318] 0x608000260600 2016-10-21 12:19:34.023 Memory[67887:5677318] 0x6080002606c0
限制
归档和解档的前提是NSArray中全部的对象都实现了NSCoding协议。
代码与结果
// 首先分别给出copy和strong修饰的属性,以NSString举例 // 一、strong @property (nonatomic, strong) NSString *str; // 二、copy @property (nonatomic, copy) NSString *str; // 分别对1和2执行下述代码 NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"123"]; self.str = mutableStr; [mutableStr appendString:@"456"]; NSLog(@"%@", self.str); NSLog(@"%p", self.str); NSLog(@"%@", mutableStr); NSLog(@"%p", mutableStr); // 结果1 2016-10-21 14:08:46.657 Memory[68242:5714288] 123456 2016-10-21 14:08:46.657 Memory[68242:5714288] 0x608000071040 2016-10-21 14:08:46.657 Memory[68242:5714288] 123456 2016-10-21 14:08:46.657 Memory[68242:5714288] 0x608000071040 // 结果2 2016-10-21 14:11:16.879 Memory[68264:5716282] 123 2016-10-21 14:11:16.880 Memory[68264:5716282] 0xa000000003332313 2016-10-21 14:11:16.880 Memory[68264:5716282] 123456 2016-10-21 14:11:16.880 Memory[68264:5716282] 0x60000007bbc0
分析
浅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"]);
可变和不可变上文谈的不是不少,由于本文认为这彻底与NSCopying和NSMutableCopying的实现息息相关。固然,对于框架类,咱们能够简单的认为,copy方法返回的就是不可变对象,mutableCopy返回的就是可变对象。若是是自定义的类,就看你怎么实现NSCopying和NSMutableCopying协议了。
首先,MRR时代用retain修饰block会产生崩溃,由于做为属性的block在初始化时是被存放在静态区的,若是block内调用外部变量,那么block没法保留其内存,在初始化的做用域内使用并不会有什么影响,但一旦出了block的初始化做用域,就会引发崩溃。全部MRC中使用copy修饰,将block拷贝到堆上。
其次,在ARC时代,由于ARC自动完成了对block的copy,因此修饰block用copy和strong都无所谓。
这个问题困惑了好久,最后只能得出一个结论,浅copy和strong引用的区别仅仅是浅copy多执行一步copyWithZone:方法。
一、https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW1
二、https://en.wikipedia.org/wiki/Object_copying