写在前面
- 浅拷贝:指向对象所在内存指针的拷贝,对象所在内存不变
- 深拷贝:对象所在内存重新拷贝一份
在 iOS 中,实现 NSObject
对象的拷贝是通过调用 - (id)copy
和 - (id)mutableCopy
,这两个方法的内部实现如下:
- (id)copy {
return [(id)self copyWithZone:nil];
}
- (id)mutableCopy {
return [(id)self mutableCopyWithZone:nil];
}
copyWithZone:
和 mutableCopyWithZone:
内部是没有实现的,分别对应 NSCopying
和 NSMutableCopying
协议
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
小结:在 iOS 中,如果要实现 NSObject
对象的拷贝功能,就需要实现 NSCopying
或 NSMutableCopying
协议。
-
NSCopying
用于不可以变对象的拷贝,比如:NSData
,NSArray
等 -
NSMutableCopying
用于可以变对象的拷贝,比如:NSMutabelArray
,NSMutableDictionary
等
常见系统类
这里主要讨论:NSNumber
,NSString
,NSMutableString
,NSArray
,NSMutabelArray
这几个主要类型,其他的类原理类似。
NSNumber
这里把NSNumber
提出来,主要是大部分的NSNumber
,在 64 位设备上大都是通过 Tagged Pointer
方式存储。Tagged Pointer
是通过把值存储在指针中,提升对小对象读取速度和节省内存的技术。因为已经没有内存的概念了,所有拷贝也不用常规的方式去思考了。
+ (void)test
{
NSNumber *num1 = @(100);
NSNumber *num2 = num1.copy;
NSLog(@"num1:%p", num1);
NSLog(@"num2:%p", num2);
}
/*
打印如下:
num1:0x837c70775f2c0953
num2:0x837c70775f2c0953
*/
可以发现通过 Tagged Pointer
存储的对象,拷贝是不生效的。在开发中主要会遇到 Tagged Pointer
优化的类有: NSString
,NSIndexPath
,NSManagedObjectID
,NSDate
当然像 NSString
通过 Tagged Pointer
来存储,条件还是比较苛刻的,字符串的长度有要求,比如
+ (void)test
{
NSString *str1 = [NSString stringWithFormat:@"a"]; // 或者 [@"a".mutableCopy copy]
NSString *str2 = str1.copy;
NSLog(@"str1:%@ %p", str1.class, str1);
NSLog(@"str2:%@ %p", str2.class, str2);
}
/*
打印如下:
str1:NSTaggedPointerString 0xb50a158d9e7f2850
str2:NSTaggedPointerString 0xb50a158d9e7f2850
*/
这里也不深究了,总结起来就是通过 Tagged Pointer
存储的对象,拷贝是不起作用的。有兴趣的可以参考我的另一篇文章 Tagged Pointer
NSString 和 NSMutableString
除了上面讨论的 Tagged Pointer
,不可变的字符串在内存中的存储也是比较特殊的。因为是一个常量,每一个不可变字符串一旦生成内存地址就固定了,所以拷贝也就没有意义了
+ (void)test
{
NSString *str1 = @"我是字符串";
NSString *str2 = str1.copy;
NSLog(@"str1:%@ %p", str1.class, str1);
NSLog(@"str2:%@ %p", str2.class, str2);
}
/*
打印如下:
str1:__NSCFConstantString 0x10979b050
str2:__NSCFConstantString 0x10979b050
*/
在不考虑这些特殊存储之外,下面再来思考 NSMutableString
对象的拷贝
+ (void)test
{
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"我是字符串"];
NSMutableString *str2 = str1.copy; // str2 为一个不可以字符串
NSMutableString *str3 = str1.mutableCopy;
NSLog(@"str1:%@ %p %@", str1.class, str1, str1);
NSLog(@"str2:%@ %p %@", str2.class, str2, str2);
NSLog(@"str3:%@ %p %@", str3.class, str3, str3);
[str1 appendString:@"++++"];
NSLog(@"str1:%@ %p %@", str1.class, str1, str1);
NSLog(@"str2:%@ %p %@", str2.class, str2, str2);
NSLog(@"str3:%@ %p %@", str3.class, str3, str3);
}
/*
打印如下:
str1:__NSCFString 0x6000017d2160 我是字符串
str2:__NSCFString 0x6000017d2070 我是字符串
str3:__NSCFString 0x6000017d2190 我是字符串
str1:__NSCFString 0x6000017d2160 我是字符串++++
str2:__NSCFString 0x6000017d2070 我是字符串
str3:__NSCFString 0x6000017d2190 我是字符串
*/
可以发现 copy
或 mutableCopy
一个可以变字符串都是深拷贝;区别在于通过 copy
生成的是不可以字符串,mutableCopy
生成的是可变字符串。
注意:如果是符合 Tagged Pointer
存储的可变字符串,调用 copy
,就会使用 Tagged Pointer
存储。
NSArray 和 NSMutabelArray
+ (void)test
{
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
NSMutableArray *arr1 = [NSMutableArray arrayWithArray:@[str1]];
NSMutableArray *arr2 = arr1.mutableCopy;
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
[str1 appendString:@"a"];
[arr1 addObject:str2];
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
}
/*
打印如下:
arr1:0x600002335b00 (a)
arr2:0x600002335da0 (a)
arr1:0x600002335b00 (aa,b)
arr2:0x600002335da0 (aa)
*/
列表对象进行了深拷贝,不过列表中的对象没有拷贝。
接下观察 NSArray
的拷贝情况
+ (void)test
{
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
NSArray *arr1 = @[str1];
NSArray *arr2 = arr1.copy;
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
[str1 appendString:@"a"];
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
}
/*
打印如下:
arr1:0x600002a46c40 (a)
arr2:0x600002a46c40 (a)
arr1:0x600002a46c40 (aa)
arr2:0x600002a46c40 (aa)
*/
地址都没有变,由于不可变的列表不能增加或删除元素,而且列表对象也不会改变,深拷贝一个列表也不能做什么,效果和不拷贝没啥差别。
接下来看可变列表和不可变列表之间的拷贝情况
+ (void)test
{
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
NSArray *arr1 = @[str1];
NSMutableArray *arr2 = arr1.mutableCopy;
NSArray *arr3 = arr2.copy;
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
NSLog(@"arr3:%p %@", arr3, arr3);
[str1 appendString:@"a"];
[arr2 addObject:str2];
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
NSLog(@"arr3:%p %@", arr3, arr3);
}
/*
打印如下:
arr1:0x600002857a40 (a)
arr2:0x60000267ea90 (a)
arr3:0x600002857a60 (a)
arr1:0x600002857a40 (aa)
arr2:0x60000267ea90 (aa,b)
arr3:0x600002857a60 (aa)
*/
可变列表和不可变列表之间的拷贝是深拷贝,同样列表中的元素不变。
如果想把列表中的元素也进行拷贝这需要使用 initWithArray:copyItems
方法
+ (void)test
{
NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
NSArray *arr1 = @[str1];
NSArray *arr2 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
NSMutableArray *arr3 = [[NSMutableArray alloc] initWithArray:arr1 copyItems:YES];;
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
NSLog(@"arr3:%p %@", arr3, arr3);
[str1 appendString:@"a"];
[arr3 addObject:str2];
NSLog(@"arr1:%p %@", arr1, arr1);
NSLog(@"arr2:%p %@", arr2, arr2);
NSLog(@"arr3:%p %@", arr3, arr3);
}
/*
打印如下:
arr1:0x600002202f80 (a)
arr2:0x600002202f60 (a)
arr3:0x600002e71350 (a)
arr1:0x600002202f80 (aa)
arr2:0x600002202f60 (a)
arr3:0x600002e71350 (a,b)
*/
initWithArray:copyItems
通过函数生成的新列表,会对列表中的调用 copy
或 mutableCopy
,所有列表中的元素需要实现 NSCopying
或 NSMutableCopying
协议。
自定义类
有了上面的基础,可以根据自己的需求实现,自定义的类的拷贝了,比如:
@interface MTObject : NSObject<NSCopying>
@property (nonatomic, strong) NSMutableString *str;
@property (nonatomic, strong) NSMutableArray *arr;
@end
@implementation MTObject
- (id)copyWithZone:(nullable NSZone *)zone
{
MTObject *copyObj = [[self class] allocWithZone:zone];
copyObj.str = self.str.mutableCopy;
copyObj.arr = [[NSMutableArray alloc] initWithArray:self.arr copyItems:YES]; //self.arr.mutableCopy 仅列表拷贝,列表中的元素不变
return copyObj;
}
@end
+ (void)test
{
NSMutableString *strInArr = @"1".mutableCopy;
MTObject *obj1 = [MTObject new];
obj1.str = @"a".mutableCopy;
obj1.arr = [NSMutableArray array];
[obj1.arr addObject:strInArr];
MTObject *obj2 = obj1.copy;
[obj1.str appendString:@"a"];
[strInArr appendString:@"1"];
NSLog(@"obj1:%p %p %@ %p %@", obj1, obj1.str, obj1.str, obj1.arr, obj1.arr);
NSLog(@"obj2:%p %p %@ %p %@", obj2, obj2.str, obj2.str, obj2.arr, obj2.arr);
}
obj1:0x600001503fe0 0x600001b7a400 aa 0x600001b7a4f0 (11)
obj2:0x600001503ec0 0x600001b7a550 a 0x600001b7a520 (1)
总结
NSObject
对象如果需要调用 copy
或 mutableCopy
方法,NSObject
对象需要实现 NSCopying
或 NSMutableCopying
协议。
- 特殊对象的存储,没有拷贝的概念
-
Tagged Pointer
方式存储 - 不可变的常量字符串
-
- 非集合类的普通对象的拷贝,一般都是对象那一层进行来深拷贝,而对象的成员变量是没有变。可以通过
NSCopying
协议,按照自己期望的方式去实现 - 集合类的普通对象的拷贝,也是只是集合对象进行来深拷贝,而集合的的对象是没有变的。不可以变的集合调用
copy
方法,不会有任何拷贝现象。
网友评论