什么是拷贝,拷贝的目的
谈到OC中的拷贝,一般是指copy && mutableCopy,或者有人说浅拷贝 && 深拷贝
谈拷贝之前,先谈一下OC中拷贝的目的
- OC拷贝的目的:
拷贝是为了使源对象产生一个副本,跟源对象互不影响:
1、修改了源对象之后不会影响到副本对象;
2、修改了副本对象,不会影响到源对象。
也就是说,克隆出一个“独立的”对象。
什么样的对象可以拷贝
那么什么样的对象可以拷贝呢?NSObject类提供了两个拷贝的方法:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
- (id)copy;
- (id)mutableCopy;
同时NSObject类也提供了两个协议,遵守协议可以调用协议内的方法:
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
分两类对象来讨论:
1、一类是系统提供的对象,比如:NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等
2、另一类是自定义的对象
不论是系统提供的对象还是自定义的对象,只要是继承自NSObject类的对象就可以进行拷贝操作。
自定义对象需要遵守NSCopying或NSMutableCopying协议。
copy && mutableCopy
- 下面以字符串为例来展开说明:
- (void)copyFunction
{
NSString *originalString = [[NSString alloc] initWithFormat:@"originalString"];
NSString *copyString = [originalString copy];
NSString *mutableString_A = [originalString mutableCopy];
NSMutableString *mutableString_B = [originalString copy];
NSLog(@"地址 originalString:%p, copyString:%p, mutableString_A:%p, mutableString_B:%p",originalString, copyString, mutableString_A, mutableString_B);
}
/*
* 打印结果:
* 地址 originalString:0x102849610,
* copyString:0x102849610,
* mutableString_A:0x102849df0,
* mutableString_B:0x102849610
*/
- (void)copyFunction
{
NSString *originalString = [[NSString alloc] initWithFormat:@"originalString"];
NSString *copyString = [originalString copy];
NSString *mutableString_A = [originalString mutableCopy];
NSMutableString *mutableString_B = [originalString copy];
[mutableString_B appendString:@"append"];
}
/*
运行结果报错:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff4d67de65 __exceptionPreprocess + 256
1 libobjc.A.dylib 0x00007fff796d9720 objc_exception_throw + 48
2 CoreFoundation 0x00007fff4d67dc97 +[NSException raise:format:] + 201
3 CoreFoundation 0x00007fff4d6bc203 mutateError + 121
4 Object-C 0x00000001000014b2 -[Person copyFunction] + 194
5 Object-C 0x00000001000015e1 main + 97
6 libdyld.dylib 0x00007fff7a7a808d start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*/
以上两个代码片段有反应出以下几个现象:
1、一个不可变字符串调用copy方法得到的字符串的地址和源字符串地址一致,并且和所接收字符串是否是可变字符串无关。
2、一个不可变字符串调用mutableCopy方法得到的字符串的地址和源字符串不一致,并且和所接收字符串是否是可变字符串无关。
3、第二个代码片段运行报错:Attempt to mutate immutable object with appendString: (通过appendString: 方法来改变不可变对象)
得到结论:
不可变对象调用copy方法后,源对象和拷贝对象指向同一块内存地址
不可变对象调用mutableCopy方法后,源对象和拷贝对象分别指向不同的内存地址
下面再看一个代码片段
- (void)copyFunction
{
NSMutableString *originalString = [[NSMutableString alloc] initWithFormat:@"originalString"];
NSString *copyString = [originalString copy];
NSMutableString *mutableString = [originalString mutableCopy];
NSLog(@"地址 originalString:%p, copyString:%p, mutableString:%p",originalString, copyString, mutableString);
}
/*
* 地址 originalString:0x1005352b0,
copyString:0x100534b20,
mutableString:0x100500700
*/
以上代码片段有反应出以下几个现象:
1、一个可变字符串调用copy方法得到的字符串的地址和源字符串地址不一致
2、一个可变字符串调用mutableCopy方法得到的字符串的地址和源字符串不一致
得到结论:
可变对象调用copy方法后,源对象和拷贝对象分别指向不同的内存地址
可变对象调用mutableCopy方法后,源对象和拷贝对象分别指向不同的内存地址
总结如下:
不论源对象是可变的还是不可变的,调用copy方法返回的就是一个不可变的副本,调用mutableCopy方法返回的就是一个可变的副本。
不可变对象调用copy方法后,源对象和拷贝对象指向同一块内存地址
不可变对象调用mutableCopy方法后,源对象和拷贝对象分别指向不同的内存地址
可变对象调用copy方法后,源对象和拷贝对象分别指向不同的内存地址
可变对象调用mutableCopy方法后,源对象和拷贝对象分别指向不同的内存地址
于是引出两个概念:深拷贝 && 浅拷贝
- 深拷贝 :内容拷贝,产生新的对象
NSString *originalString = [[NSString alloc] initWithFormat:@"originalString"];
NSMutableString *mutableString = [originalString mutableCopy];
mutableString 拷贝了originalString的内容
originalString 和 mutableString 分别指向两个不同的内存地址
originalString 的内容为不可变的
mutableString 的内容为可变的
NSMutableString *originalString = [[NSMutableString alloc] initWithFormat:@"originalString"];
NSString *copyString = [originalString copy];
NSMutableString *mutableString = [originalString mutableCopy];
copyString 和 mutableString 拷贝了 originalString 的内容
originalString 、copyString 和 mutableString 分别指向三个不同的内存地址
originalString 的内容为可变的
copyString 的内容为不可变的
mutableString 的内容为可变的
- 浅拷贝 :指针拷贝,不产生新的对象
NSString *originalString = [[NSString alloc] initWithFormat:@"originalString"];
NSString *copyString = [originalString copy];
originalString 和 copyString指向同一块内存地址,保存的内容为不可变的
下面再来查看一下 copy 和 mutableCopy 的内存调用情况
- (void)copyFunction
{
NSString *originalString = [[NSString alloc] initWithFormat:@"originalString"];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = 1
NSString *copyString = [originalString copy];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = 2
NSLog(@"copyString retain count = %zd",copyString.retainCount); // retainCount = 2
NSMutableString *mutableCopyString = [originalString mutableCopy];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = 2
NSLog(@"copyString retain count = %zd",copyString.retainCount); // retainCount = 2
NSLog(@"mutableCopyString retain count = %zd",mutableCopyString.retainCount); // retainCount = 1
[mutableCopyString release];
[copyString release];
[originalString release];
}
- 创建originalString字符串,originalString的引用计数为1
- originalString 调用copy方法,拷贝字符串给copyString ,
originalString 和 copyString 指向同一块内存地址,这块内存有两个指针指向,引用计数为2。originalString和copyString的引用计数都为2。此时 copy 相当于 retain,只是引用计数+ 1 - originalString 调用mutableCopy方法,深拷贝字符串给mutableCopyString
mutableCopyString指向新的一块内存地址,引用计数为1
originalString 指向的内存地址没有新的指针指向,引用计数仍然为 2
但是可能有人打印retain count = -1,例如下面代码块:
- (void)copyFunction
{
NSString *originalString = [[NSString alloc] initWithFormat:@"abc"];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = -1
NSString *copyString = [originalString copy];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = -1
NSLog(@"copyString retain count = %zd",copyString.retainCount); // retainCount = -1
NSMutableString *mutableCopyString = [originalString mutableCopy];
NSLog(@"originalString retain count = %zd",originalString.retainCount); // retainCount = -1
NSLog(@"copyString retain count = %zd",copyString.retainCount); // retainCount = -1
NSLog(@"mutableCopyString retain count = %zd",mutableCopyString.retainCount); // retainCount = 1
[mutableCopyString release];
[copyString release];
[originalString release];
NSLog(@"地址 originalString:%p, copyString:%p, mutableString:%p",originalString, copyString, mutableCopyString);
// 地址 originalString:0x8f08b0bf23c20133, copyString:0x8f08b0bf23c20133, mutableString:0x100508a50
NSLog(@"类型 originalString:%@, copyString:%@, mutableString:%@",[originalString className], [copyString className], [mutableCopyString className]);
// 类型 originalString:NSTaggedPointerString, copyString:NSTaggedPointerString, mutableString:__NSCFString
}
对比上面两个代码块,发现除了originalString赋值的内容不一样,其他没有任何改变。但是打印retain count 获得到的结果不同。
originalString 和 copyString 的 retainCount 均为 -1 ,mutableCopyString的retainCount扔为 1。
打印地址和类型发现
originalString 和 copyString 的地址为 :0x8f08b0bf23c20133 mutableCopyString 的地址为:0x100508a50 很明显这两个地址相差很大,应该不是同一类型。
originalString 和 copyString 类型为NSTaggedPointerString,mutableCopyString的类型为__NSCFString
那么 NSTaggedPointerString 是什么类型,为什么会影响到retain count ?
- Tagged Pointer
从arm64开始,iOS引入了TaggedPointer技术,用于优化NSNumber、NSData、NSString等小对象的存储
在没有使用TaggedPointer之前,NSNumber等对象需要动态分配内存,维护引用计数。NSNumber指针存储的是堆中NSNumber对象的地址值
使用TaggedPointer之后,NSNumber指针里面存储的数据变成了Tag+Data,也就是将数据直接存储在了指针中,这样就不用动态分配内存地址,也不用维护引用计数,节省了之前的调用开销。
系统会根据NSString的内容长短自动决定是NSTaggedPointerString类型还是__NSCFString类型。由于@"abc"长度可以存储在指针中,所以originalString 和 copyString 类型为NSTaggedPointerString。
详细讲解Tagged Pointer会另起一篇介绍。
NSArray、NSMutableArray、NSDictionary、NSMutableDictionary 调用 copy、mutableCopy的情况是怎样?
NSArray、NSMutableArray、NSDictionary、NSMutableDictionary 调用 copy、mutableCopy 和 NSString 、NSMutableString调用时遵循的规则一致。
类型 | copy | mutableCopy |
---|---|---|
NSString | NSString(浅拷贝) | NSMutableString(深拷贝) |
NSMutableString | NSString(深拷贝) | NSMutableString(深拷贝) |
NSArray | NSArray(浅拷贝) | NSMutableArray(深拷贝) |
NSMutableArray | NSArray(深拷贝) | NSMutableArray(深拷贝) |
NSDictionary | NSDictionary(浅拷贝) | NSMutableDictionary(深拷贝) |
NSMutableDictionary | NSDictionary(深拷贝) | NSMutableDictionary(深拷贝) |
- 自定义对象实现拷贝
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
Person *copyPerson = [person copy];
}
return 0;
}
/*
* -[Person copyWithZone:]: unrecognized selector sent to instance 0x10051c890
* *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person copyWithZone:]: unrecognized selector sent to instance 0x10051c890'
*/
如果直接将自定义的对象调用copy方法,会报错,提示Persong类中没有对象方法copyWithZone:
解决方法是,People类遵守NSCopying 或 NSMutableCopying协议,并实现copyWithZone: 或mutableCopyWithZone:方法
@interface Person ()<NSCopying,NSMutableCopying>
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
Person *person = [[Person alloc] init];
person.age = 50;
return person;
}
@end
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.number = 1;
Person *copyPerson = [person copy];
copyPerson.age = 20;
copyPerson.number = 2;
Person *mutableCopyPeople = [person mutableCopy];
mutableCopyPeople.age = 30;
mutableCopyPeople.number = 3;
NSLog(@"person age = %d, number = %d",person.age, person.number);
// person age = 20, number = 2
NSLog(@"copyPerson age = %d, number = %d",copyPerson.age, copyPerson.number);
// copyPerson age = 20, number = 2
NSLog(@"mutableCopyPeople age = %d, number = %d",mutableCopyPeople.age, mutableCopyPeople.number);
// mutableCopyPeople age = 30, number = 3
NSLog(@"地址 person : %p, copyPerson : %p, mutableCopyPeople : %p",person, copyPerson, mutableCopyPeople);
// 地址 person : 0x100631a90, copyPerson : 0x100631a90, mutableCopyPeople : 0x100632020
}
return 0;
}
- 手动实现copy修饰的setter方法
@interface Person ()
@property (nonatomic, copy) NSArray *array;
@end
@implementation Person
- (void)setArray:(NSArray *)array
{
if (_array != array) {
[_array release];
_array = [array copy];
}
}
@end
但是如果用copy修饰一个可变类型的对象时会出现什么问题?
@interface Person ()
@property (nonatomic, copy) NSMutableArray *mutableArray;
@end
@implementation Person
- (void)addObject {
self.mutableArray = [NSMutableArray array];
[self.mutableArray addObject:@"A"];
}
@end
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person addObject];
}
return 0;
}
/*
* -[__NSArray0 addObject:]: unrecognized selector sent to instance 0x100506f60
* *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x100506f60'
*/
发现报错:NSArray无法调用addObject:方法,为什么会出现这种情况?
因为 mutableArray 是用copy来修饰的,那么
self.mutableArray = [NSMutableArray array]; 相当于:
_mutableArray = [可变数组 copy];
于是 _mutableArray 就变成了一个不可变数组,自然就没有addObject: 方法。