在程序开发中, 我们经常会遇到 浅拷贝 深拷贝 的问题, 针对堆中的数据, 我们会有栈上的指针指向对象所在的地址, 当我们需要对堆上的对象进行拷贝时, 有两个选择
- 浅拷贝: 只复制指向某个对象的指针, 而不复制对象本身, 新旧对象还是共享同一块内存
- 深拷贝: 会另外创造一个一模一样的对象, 新对象跟原对象不共享内存, 修改新对象不会改到原对象
但是在 iOS 开发中, 也会经常遇到深浅拷贝问题, 比如我有一个对象, 想复制一份, 如果直接使用指针, 其实是浅拷贝, 只是指向同一个内存位置, 同时该对象的引用计数会 +1
1 2 3 4 5 6
| WYPerson *person = [WYPerson new]; WYPerson *person1 = person;
NSLog(@"person: %@", person); NSLog(@"person1: %@", person1); NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)person));
|
输出
1 2 3
| person: <WYPerson: 0x600000011290> person1: <WYPerson: 0x600000011290> Retain count is 2
|
在 iOS 想复制对象, 需要实现 NSCopying
协议
1 2 3 4 5
| - (id)copyWithZone:(nullable NSZone *)zone { WYPerson *person = [[WYPerson allocWithZone:zone] init]; return person; }
|
测试代码改成
1 2 3 4 5 6 7 8 9 10
| WYPerson *person = [WYPerson new]; WYPerson *person1 = person; WYPerson *person2 = person.copy;
NSLog(@"person: %@", person); NSLog(@"person1: %@", person1); NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)person));
NSLog(@"person2: %@", person2); NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)person2));
|
输出
1 2 3 4 5
| person: <WYPerson: 0x600000018990> person1: <WYPerson: 0x600000018990> Retain count is 2 person2: <WYPerson: 0x600000018840> Retain count is 1
|
这样就完成了一次深拷贝.
对一个 OC 对象的深浅拷贝很好理解, 那如果是 NSString / NSNumber / NSArray / NSDictionary 呢?
NSString / NSNumber
NSString / NSNumber 比较特殊的. iOS 存在 Tagged Pointer
概念, 详见ARC 与 AutoReleasePool - “Tagged Pointer” 章节, 即用指针直接存储小的数字或者字符串.
直接上测试代码
1 2 3 4 5 6 7 8 9 10
| NSNumber *number = @123; NSNumber *number1 = number; NSNumber *number2 = number.copy;
NSLog(@"string: %p", number); NSLog(@"string1: %p", number1); NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)number));
NSLog(@"string2: %p", number2); NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)number2));
|
输出
1 2 3 4 5
| string: 0x94739e52bcf88440 string1: 0x94739e52bcf88440 Retain count is 9,223,372,036,854,775,807 string2: 0x94739e52bcf88440 Retain count is 9,223,372,036,854,775,807
|
可见对 NSNumber / NSString 直接复制都是浅拷贝. (指针以 0x9开头 表示第一位是 1, 即 Tagged Pointer
)
判断指针是不是 Tagged Pointer
就很简单了
1 2 3 4 5 6
| #define _OBJC_TAG_MASK (1UL<<63) NS_INLINE BOOL isTaggedPointer(const void *ptr) { return ((uintptr_t)ptr & (1UL<<63)) == _OBJC_TAG_MASK; }
NSLog(@"number isTaggedPointer: %d", isTaggedPointer((__bridge void *)number));
|
NSString 指针不是Tagged Pointer
, 是因为系统会单独缓存管理所有的 NSString, 对 NSString 使用 copy 仍然是浅拷贝.
NSString / NSNumber 深拷贝 - mutableCopy
NSNumber 不支持 mutableCopy, 但是 NSString 支持
1 2 3 4 5 6 7 8 9 10 11
| NSString *string = @"123"; NSString *string1 = string.copy; NSString *string2 = string.mutableCopy; NSString *string3 = string2.copy; NSString *string4 = string3.copy;
NSLog(@"string: %p, %@", string, NSStringFromClass(string.class)); NSLog(@"string1: %p, %@", string1, NSStringFromClass(string1.class)); NSLog(@"string2: %p, %@", string2, NSStringFromClass(string2.class)); NSLog(@"string3: %p, %@", string3, NSStringFromClass(string3.class)); NSLog(@"string4: %p, %@", string4, NSStringFromClass(string4.class));
|
输出
1 2 3 4 5
| string: 0x104d403e0, __NSCFConstantString string1: 0x104d403e0, __NSCFConstantString string2: 0x282d87c60, __NSCFString string3: 0xa69c7b78fa4e55da, NSTaggedPointerString string4: 0xa69c7b78fa4e55da, NSTaggedPointerString
|
其中 __NSCFString
是 NSMutableString 的子类, 即 mutableCopy 后, NSString 转化为了 NSMutableString.
对 可变字符串string2 copy
后产生string3, string3 是 NSTaggedPointerString
, 表示 string3 是Tagged Pointer
(看指针的值第一位0xa
也看得出来)
对 string3(Tagged Pointer
)再次执行 copy, 指针指不变, 跟前面 NSNumber 一致. (如果这个字符串太大, 就不会变成Tagged Pointer
)
其实这里 __NSCFConstantString
是 __NSCFString
的子类, 这是一个类簇(Class Clusters
)的思想
1 2 3 4 5
| NSString ├── NSMutableString │ └── __NSCFString │ └── __NSCFConstantString └── NSTaggedPointerString
|
NSArray / NSDictionary / NSSet
直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| WYPerson *person1 = [[WYPerson alloc] init]; WYPerson *person2 = [[WYPerson alloc] init]; WYPerson *person3 = [[WYPerson alloc] init]; NSArray *array = @[person1, person2, person3]; NSArray *copyArray = [array copy]; NSArray *mutableCopyArray = [array mutableCopy]; NSArray *mutableCopyArrayCopy = [mutableCopyArray copy]; NSArray *mutableCopyArrayMutableCopy = [mutableCopyArray mutableCopy];
NSLog(@"array(%@<%p>: %p): %@", [array class], &array, array, array); NSLog(@"copyArray(%@<%p>: %p): %@", [copyArray class], ©Array, copyArray, copyArray); NSLog(@"mutableCopyArray(%@<%p>: %p): %@", [mutableCopyArray class], &mutableCopyArray, mutableCopyArray, mutableCopyArray); NSLog(@"mutableCopyArrayCopy(%@<%p>: %p): %@", [mutableCopyArrayCopy class], &mutableCopyArrayCopy, mutableCopyArrayCopy, mutableCopyArrayCopy); NSLog(@"mutableCopyArrayMutableCopy(%@<%p>: %p): %@", [mutableCopyArrayMutableCopy class], &mutableCopyArrayMutableCopy, mutableCopyArrayMutableCopy, mutableCopyArrayMutableCopy);
|
输出
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
| array(__NSArrayI<0x16d0d32b8>: 0x280335d70): ( "<WYPerson: 0x280f4d830>", "<WYPerson: 0x280f4d820>", "<WYPerson: 0x280f4d850>" ) copyArray(__NSArrayI<0x16d0d32b0>: 0x280335d70): ( "<WYPerson: 0x280f4d830>", "<WYPerson: 0x280f4d820>", "<WYPerson: 0x280f4d850>" ) mutableCopyArray(__NSArrayM<0x16d0d32a8>: 0x280335f50): ( "<WYPerson: 0x280f4d830>", "<WYPerson: 0x280f4d820>", "<WYPerson: 0x280f4d850>" ) mutableCopyArrayCopy(__NSArrayI<0x16d0d32a0>: 0x280335f80): ( "<WYPerson: 0x280f4d830>", "<WYPerson: 0x280f4d820>", "<WYPerson: 0x280f4d850>" ) mutableCopyArrayMutableCopy(__NSArrayM<0x16d0d3298>: 0x280335fb0): ( "<WYPerson: 0x280f4d830>", "<WYPerson: 0x280f4d820>", "<WYPerson: 0x280f4d850>" )
|
NSArray 的类簇参考
1 2 3 4
| └── NSArray ├── NSMutableArray │ └── __NSArrayM └── __NSArrayI
|
- array,
NSArray
初始化类型是__NSArrayI
, 是 NSArray
的子类
- copyArray,
__NSArrayI
copy 是浅拷贝, 指针指仍然是 0x280e0dda0
- mutableCopyArray
__NSArrayI
mutableCopy 后产生__NSArrayM
, 是深拷贝
- mutableCopyArrayCopy
__NSArrayM
copy 后产生__NSArrayI
, 是深拷贝
- mutableCopyArrayMutableCopy
__NSArrayM
mutableCopy 后产生__NSArrayM
, 还是深拷贝
同时不管怎么操作都没有集合内的元素, 即WYPerson
对象没有被拷贝
NSDictionary / NSSet 实现也类似, 可以总结为
类名 |
操作 |
新对象(深拷贝) |
新类名 |
集合内元素拷贝 |
NS* |
copy |
NO(浅拷贝) |
NS* |
NO |
NS* |
mutableCopy |
YES(深拷贝) |
NSMutable* |
NO |
NSMutable* |
copy |
YES(深拷贝) |
NS* |
NO |
NSMutable* |
mutableCopy |
YES(深拷贝) |
NSMutable* |
NO |
完全深拷贝
我们业务经常需要完全深拷贝, 即不仅容器本身需要拷贝, 容器中的元素也要被拷贝, 参考WYCollectionMutableDeepCopy.m 源码 处理如下:
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 33
| -(NSMutableDictionary *)wyMutableDeepCopy { NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:[self count]]; [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { id copyValue = nil; if ([obj respondsToSelector:@selector(wyMutableDeepCopy)]) { copyValue = [obj performSelector:@selector(wyMutableDeepCopy)]; } else if ([obj conformsToProtocol:@protocol(NSMutableCopying)] && [obj respondsToSelector:@selector(mutableCopyWithZone:)]) { copyValue = [obj mutableCopy]; } else if ([obj conformsToProtocol:@protocol(NSCopying)] && [obj respondsToSelector:@selector(copyWithZone:)]) { copyValue = [obj copy]; } if (copyValue) { [dict setObject:copyValue forKey:key]; } else { [dict setObject:obj forKey:key]; NSAssert2(NO, @"NSDictionary (MutableDeepCopy) dict:%@ errorKey:%@",self , key); } }]; return dict; }
|