Copy 在 Objective-C 中的使用,一些个人理解。
Object Copying
OC 中,一个对象,如果遵循 NSCopying 协议,且实现了 copyWihtZone:
方法,就可以被拷贝。
如果类具有不可变和可变两种变体,通过遵循 NSMutableCopying 协议,并实现 mutableCopyWithZone:
来确保可变对象的拷贝也是可变的
Copy 分为 shadow copy 和 deep copy,这两种 copy 的区别,主要在堆指针的处理:
- shadow copy 仅复制对象的引用指针
- deep copy 复制新的对象
注意:
对象的 copy,不能仅仅看内存地址来分辨是 shadow 还是 deep。
比如 NSString,对其使用 copy,返回地址与原对象相同,但是两个对象是完全独立的,相互修改不影响
Object Copying Memory Management
对象复制,会返回一个引用计数为 1 的新对象。
这个复制的对象的内存管理,谁引用谁负责释放。
copy 修饰属性
copy 作为 内存管理语义 ,在 @property 中声明,其目的是:复制外部的传过来的值并隔断之后外界值修改对属性的影响。
对应的, strong(retain) 修饰词,只是将外部值或者指针赋值给属性(shadow copy),如果是对象指针,之后外部值修改会影响属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @property (nonatomic, copy) NSString *copyStr; - (void)setCopyStr:(NSString *)copyStr { if (_copyStr != copyStr) { [_copyStr release]; _copyStr = [copyStr copy]; } }
@property (nonatomic, strong) NSString *strongStr; - (void)setStrongStr:(NSString *)strongStr { if (_strongStr != strongStr) { [_strongStr release]; [strongStr retain]; _strongStr = strongStr; } }
|
观察赋值方法,引申出一个小知识点:
使用self.foo = foo;
语法赋值,调用了 setter 方法;使用_foo = foo;
则只是改变指针指向。
如果只是简单的变更指针指向,可能会导致可变对象和不可变对象混淆。
因此,更推荐使用self.
语法进行赋值
在 OC 中,因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响。
使用 copy 修饰 NSString,无论传入是一个可变对象还是不可对象,对象持有的就是一个不可变的副本.
注意:无论原来的 string 类型是否可变,copy 后的类型均为不可变类型。
NSArray、NSDictionary、NSSet 属于 collection 集合类对象。
这些集合类的属性,使用 copy 修饰,只能保证容器本身的不变,如果容器内的引用对象,发生了变化,这些容器属性中的对象也会发生变化。
block 使用 copy 是从 MRC 遗留下来的“传统”。
在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区。
在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 可以提示:编译器自动对 block 进行了 copy 操作。
如果 block 块没有访问处于栈区的变量(比如局部变量),也没有访问堆区的变量(比如我们 alloc 创建的对象),那就存在代码区(低地址),即使访问了全局变量,也依然存在代码区。
如果访问了栈区或者堆区的变量,那就会被存在堆区(实际存在栈区,ARC 下会自动拷贝到堆区)。
copy mutableCopy 方法
主动调用 copy
方法,其目的是为了 copy 一个新对象。
此时需要根据对象的类实现的 copyWithZone:
方法来判断是 shadow 还是 deep。
对 immutable 对象进行 copy 操作, 执行结果类似 retain 操作,但操作的目的是为了 copy 对象。
对 immutable 对象进行 mutableCopy 操作时内容复制。
对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。
1 2 3 4
| [immutableObject copy] [immutableObject mutableCopy] [mutableObject copy] [mutableObject mutableCopy]
|
和上面非集合类对象十分类似。
但是无论是 copy 还是 mutableCopy,集合中的元素,都是仅仅执行了一次 retain 操作,依然是原对象
1 2 3 4
| [immutableCollection copy] [immutableCollection mutableCopy] [mutableCollection copy] [mutableCollection mutableCopy]
|
copy collection
当我们对集合类型的对象,使用 copy 或者 mutableCopy,都是对集合对象本身的拷贝,集合中的元素,都仅仅是一个 retain 操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| UIView *view1 = [UIView new]; UIView *view2 = [UIView new]; UIView *view3 = [UIView new];
NSArray *array = @[view1, view2, view3,]; NSArray *copyArray = [array copy]; NSMutableArray *mCopyArray = [array mutableCopy];
NSLog(@"array %p \n %@", array, array); NSLog(@"copy %p \n %@", copyArray, copyArray); NSLog(@"mutableCopy %p \n %@", mCopyArray, mCopyArray);
输出结果: array 0x600003da2ca0 ( "<ObjectCopyingCoding: 0x6000033feca0>", "<ObjectCopyingCoding: 0x6000033fede0>", "<ObjectCopyingCoding: 0x6000033feda0>" ) copy 0x600003da2ca0 ( "<ObjectCopyingCoding: 0x6000033feca0>", "<ObjectCopyingCoding: 0x6000033fede0>", "<ObjectCopyingCoding: 0x6000033feda0>" ) mutableCopy 0x600003da2af0 ( "<ObjectCopyingCoding: 0x6000033feca0>", "<ObjectCopyingCoding: 0x6000033fede0>", "<ObjectCopyingCoding: 0x6000033feda0>" )
|
如何对集合对象进行 deep copy 呢?
首先,需要集合对象中的元素,实现 NSCopying 协议
使用 initWithArray:copyItems:
1 2 3 4 5 6 7 8 9 10
| NSArray *oneLevelDeepCopy = [[NSArray alloc] initWithArray:array copyItems:YES]; NSLog(@"oneLevelDeepCopy %p \n %@", oneLevelDeepCopy, oneLevelDeepCopy);
输出结果: oneLevelDeepCopy 0x600003d9ef10 ( "<ObjectCopyingCoding: 0x6000033b47a0>", "<ObjectCopyingCoding: 0x6000033b4780>", "<ObjectCopyingCoding: 0x6000033b4760>" )
|
注意:initWithArray:copyItems:
这个方法,只能对一元数组进行 deep copy。
apple 推荐使用 NSKeyedArchiver 实现 true deep copy。需要集合对象中的元素,实现 NSCoding 协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| NSArray *twoLevelArray = @[@[obj1, obj2, obj3,]]; NSLog(@"twoLevelArray %p \n %@", twoLevelArray, twoLevelArray); NSArray *oneLevelDeepCopy2 = [[NSArray alloc] initWithArray:twoLevelArray copyItems:YES]; NSLog(@"oneLevelDeepCopy2 %p \n %@", oneLevelDeepCopy2, oneLevelDeepCopy2);
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:array]]; NSLog(@"trueDeepCopyArray %p \n %@", trueDeepCopyArray, trueDeepCopyArray);
输出结果 twoLevelArray 0x6000031f07e0 ( ( "<ObjectCopyingCoding: 0x6000033feca0>", "<ObjectCopyingCoding: 0x6000033fede0>", "<ObjectCopyingCoding: 0x6000033feda0>" ) ) oneLevelDeepCopy2 0x6000031e3c20 ( ( "<ObjectCopyingCoding: 0x6000033feca0>", "<ObjectCopyingCoding: 0x6000033fede0>", "<ObjectCopyingCoding: 0x6000033feda0>" ) ) trueDeepCopyArray 0x600003d9ae80 ( "<ObjectCopyingCoding: 0x6000033e6e00>", "<ObjectCopyingCoding: 0x6000033e6e20>", "<ObjectCopyingCoding: 0x6000033e4e40>" )
|
参考文档
Object Copying
Copying Collections