iOS 中的 浅拷贝 深拷贝 以及 完全深拷贝

在程序开发中, 我们经常会遇到 浅拷贝 深拷贝 的问题, 针对堆中的数据, 我们会有栈上的指针指向对象所在的地址, 当我们需要对堆上的对象进行拷贝时, 有两个选择

  • 浅拷贝: 只复制指向某个对象的指针, 而不复制对象本身, 新旧对象还是共享同一块内存
  • 深拷贝: 会另外创造一个一模一样的对象, 新对象跟原对象不共享内存, 修改新对象不会改到原对象

但是在 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], &copyArray, 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
  1. array, NSArray 初始化类型是__NSArrayI, 是 NSArray的子类
  2. copyArray, __NSArrayI copy 是浅拷贝, 指针指仍然是 0x280e0dda0
  3. mutableCopyArray __NSArrayI mutableCopy 后产生__NSArrayM, 是深拷贝
  4. mutableCopyArrayCopy __NSArrayM copy 后产生__NSArrayI, 是深拷贝
  5. 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]];
//新建一个NSMutableDictionary对象,大小为原NSDictionary对象的大小
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
id copyValue = nil;
if ([obj respondsToSelector:@selector(wyMutableDeepCopy)])
{
//如果key对应的元素可以响应wyMutableDeepCopy方法(还是NSDictionary),调用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;
}
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道