浅谈iOS Copy与MutableCopy
写了这么久的iOS,copy与mutableCopy也用了不少,可究竟什么时候用copy,什么时候用mutablecopy,他俩区别在哪,一直一知半解,也是论坛上的某些大神说这里用copy,那就用copy,比如NSString用copy修饰符。
不谈从网上看到的深拷贝,浅拷贝概念,先从代码看起,但开始代码之前,还必须知道一个必须知道的概念:
要实现Copy 与MutableCopy必须要遵循Copy,MutableCopy协议
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
如果没有实现协议,调用copy
,mutableCopy
方法会Crash
.
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
遵循NSCopying协议,就是实现copyWithZone
方法,而关于copyWithZone
返回值id:Returns a new instance that’s a copy of the receiver.
返回一个新实例
关于返回值的说明:
The returned object is implicitly retained by the sender, who is
responsible for releasing it. The copy returned is immutable if the
consideration “immutable vs. mutable” applies to the receiving
object; otherwise the exact nature of the copy is determined by the
class.
大意就是不管原始对象是不可变的或者是可变的,copy
返回的都是不可变对象,copy
之后的对象性质由原始对象确定。
同理,NSMutableCopying
遵循协议
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
返回值id:Returns a new instance that’s a mutable copy of the receiver.
返回一个新实例
关于返回值说明
The returned object is implicitly retained by the sender, which is
responsible for releasing it.
The copy returned is mutable whether the original is mutable or not.
即不管原始对象是不是可变的,返回对象都是可变的。
NSString copy mutableCopy
NSMutableString *string = [NSMutableString stringWithString:@"copy test"];
NSString *origin = [NSString stringWithString:string];
NSString *copy = [origin copy];
NSMutableString *mut = [origin mutableCopy];
NSLog(@"原始地址 %p, %@",origin,origin);
NSLog(@"copy地址 %p, %@",copy,copy);
NSLog(@"mut copy 地址 %p, %@",mut,mut);
[string appendString:@"aaa"];
[mut appendString:@"ffff"];
NSLog(@"原始地址 %p, %@",origin,origin);
NSLog(@"copy地址 %p, %@",copy,copy);
NSLog(@"mut copy 地址 %p, %@",mut,mut);
输出结果
原始地址 0x883205f046fa5f3f, copy test
copy地址 0x883205f046fa5f3f, copy test
mut copy 地址 0x6000015efab0, copy test
原始地址 0x883205f046fa5f3f, copy test
copy地址 0x883205f046fa5f3f, copy test
mut copy 地址 0x6000015efab0, copy testffff
我们分别对俩个可以改变值进行拼接修改,[NSString stringWithString:string]
方法执行的是开辟一个新的空间并拷贝string
指向的值。
而copy
不管是原始字符串或copy
后的字符串,都是不可变的,所以拷贝只需要将指针指向原始字符串的地址空间即可,即俩者是同一个玩意。
mutableCopy
则需要开辟一个新的地址空间保存拷贝后的可变字符串所以地址必然变化,而由实验可知,mut
改变并不会影响原来的值,得知值也是重新拷贝了一份
结论
对于不可变字符串copy
之后的其实就是本身,而mutableCopy
会将整个对象拷贝一份
再来看NSMutableString
NSMutableString copy mutableCopy
NSMutableString *string = [NSMutableString stringWithString:@"copy test"];
NSMutableString *originString = [[NSMutableString alloc] initWithString:string];
NSString *copystring = [originString copy];
NSMutableString *mutystring = [originString mutableCopy];
NSLog(@"原始地址 %p, %@",originString,originString);
NSLog(@"copy地址 %p, %@",copystring,copystring);
NSLog(@"mut copy地址 %p, %@",mutystring,mutystring);
[string appendString:@"xxxxxxx"];
[originString appendString:originString];
[mutystring appendString:@"0000000"];
NSLog(@"修改后地址 %p, %@",originString,originString);
NSLog(@"修改后copy地址 %p, %@",copystring,copystring);
NSLog(@"修改后mutcopy地址 %p, %@",mutystring,mutystring);
输出结果
原始地址 0x600002a55ef0, copy test
copy地址 0xf701af19378c9fa0, copy test
mut copy地址 0x600002a55bf0, copy test
修改后地址 0x600002a55ef0, copy testcopy test
修改后copy地址 0xf701af19378c9fa0, copy test
修改后mutcopy地址 0x600002a55bf0, copy test0000000
由修改前输出地址可知,对于可变字符串,不管是copy
,mutableCopy
都会开辟新的内存空间去保存拷贝后的地址
通过修改可变字符串之后可知,俩种拷贝都会对值进行拷贝
结论
对可变字符串,copy
,mutableCopy
都会将整个对象重新拷贝
NSString
为什么用推荐copy
比如:
一个动物类,有一个属性name
,如果给name
赋值的是不可变类型,皆大欢喜,copy
与否都是一样的,但是当赋值的是可变类型的话
NSMutableString *originString = [[NSMutableString alloc] initWithString:@"深拷贝,浅拷贝"];
self.name = originString;
[originString appendString:@"pengshuai"];
self.name
会跟着变动,而copy
不会有这种顾虑,而且不管name
是可变,或不可变,使用copy
不会相互影响。
看来看数组拷贝:
NSArray NSMutableArray
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"yao",@"peng", nil];
NSArray *originArr = [NSArray arrayWithObject:array];
NSArray *copyArr = [originArr copy];
NSMutableArray *mutableArr = [originArr mutableCopy];
NSLog(@"原始数组%p %@",originArr,originArr);
NSLog(@"Copy数组%p %@",copyArr,copyArr);
NSLog(@"MutableCopy数组%p %@",mutableArr,mutableArr);
[array addObject:@"juan"];
[mutableArr addObject:@"xx00"];
NSLog(@"修改数组%p %@",originArr,originArr);
NSLog(@"修改Copy数组%p %@",copyArr,copyArr);
NSLog(@"修改MutableCopy数组%p %@",mutableArr,mutableArr);
输出结果
原始数组0x6000001e8da0 (
yao,
peng
)
Copy数组0x6000001e8da0 (
yao,
peng
)
MutableCopy数组0x600000ff8330 (
yao,
peng
)
修改数组0x6000001e8da0 (
yao,
peng
)
修改Copy数组0x6000001e8da0 (
yao,
peng
)
修改MutableCopy数组0x600000ff8330 (
yao,
peng,
xx00
)
得出的结果和string
的copy
类型基本一样
copy
两者一样,mutableCopy
对象拷贝一份
再看NSMutableArray
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"yao",@"peng", nil];
NSMutableArray *originArray = [NSMutableArray arrayWithArray:array];
NSArray *copyArray = [originArray copy];
NSMutableArray *mutableArray = [originArray mutableCopy];
NSLog(@"原始数组%p %@",originArray,originArray);
NSLog(@"Copy数组%p %@",copyArray,copyArray);
NSLog(@"MutableCopy数组%p %@",mutableArray,mutableArray);
[array addObject:@"juan"];
[originArray addObject:@"xxx"];
[mutableArray addObject:@"000"];
NSLog(@"修改数组%p %@",originArray,originArray);
NSLog(@"修改Copy数组%p %@",copyArray,copyArray);
NSLog(@"修改MutableCopy数组%p %@",mutableArray,mutableArray);
输出结果
原始数组0x600001ec8810 (
yao,
peng
)
Copy数组0x6000010daf40 (
yao,
peng
)
MutableCopy数组0x600001ec86f0 (
yao,
peng
)
修改数组0x600001ec8810 (
yao,
peng,
xxx
)
修改Copy数组0x6000010daf40 (
yao,
peng
)
修改MutableCopy数组0x600001ec86f0 (
yao,
peng,
000
)
对于可变数组,可以看到不管哪种copy
,都会对对象重新拷贝,和可变字符串一样啊,但是,数组容器有一个变数,如下:
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"yao",@"peng", nil];
NSMutableArray *originArray = [NSMutableArray arrayWithObject:array];
NSArray *copyArray = [originArray copy];
NSMutableArray *mutableArray = [originArray mutableCopy];
NSLog(@"原始数组%p %@",originArray,originArray);
NSLog(@"Copy数组%p %@",copyArray,copyArray);
NSLog(@"MutableCopy数组%p %@",mutableArray,mutableArray);
[array addObject:@"juan"];
[originArray addObject:@"xxx"];
[mutableArray addObject:@"000"];
NSLog(@"修改数组%p %@",originArray,originArray);
NSLog(@"修改Copy数组%p %@",copyArray,copyArray);
NSLog(@"修改MutableCopy数组%p %@",mutableArray,mutableArray);
在看结果
原始数组0x60000177d530 (
(
yao,
peng
)
)
2019-01-03 16:15:08.055989+0800 ddd[16899:1986944] Copy数组0x600001b3c950 (
(
yao,
peng
)
)
2019-01-03 16:15:08.056074+0800 ddd[16899:1986944] MutableCopy数组0x60000177d1d0 (
(
yao,
peng
)
)
修改数组0x60000177d530 (
(
yao,
peng,
juan
),
xxx
)
修改Copy数组0x600001b3c950 (
(
yao,
peng,
juan
)
)
修改MutableCopy数组0x60000177d1d0 (
(
yao,
peng,
juan
),
000
)
其他都类似,但是改变array
的时候,不管哪种拷贝,数组也会跟着改变,是因为数组复制,其元素对象始终是指针复制,元素指向的值改变,数组自然都会改变。
由以上实验大概得出以下结论
非集合对象 | copy | mutable copy |
---|---|---|
不可变对象 | 指针拷贝 | 指针拷贝,值拷贝 |
可变对象 | 指针拷贝,值拷贝 | 指针拷贝,值拷贝 |
集合对象 | copy | mutable copy |
---|---|---|
不可变对象 | 指针拷贝 | 指针拷贝,元素对象指针拷贝 |
可变对象 | 指针拷贝,元素对象指针拷贝 | 指针拷贝,元素对象指针拷贝 |
Block 与 Copy
使用block
属性的时候一般也使用copy
,一般情况下,block默认保存在栈区,在对外部对象进行操作时,不会对对象进行retain,而当block保存在堆区时,在外部对象进行操作时,会对对象进行retain。而我们本是是不知道什么时候什么时候调用block的,当block中的对象提前释放,会造成Crash,使用copy关键字能保住block,防止调用的时候Crash.