阴差阳错,前两天和一个小伙伴在一起聊天。聊到关于 copy
和 strong
的问题。这个在ARC[Automatic Reference Counting)]
下慢慢淡化的一个东东。交流之中让我受益匪浅,原来 copy
和 strong
还可以这么玩。
以下内容在 demo 中均有体现
1.首先我们先看一下到底出现了什么问题
使用 copy 修饰这个可变数组
@property (copy, nonatomic) NSMutableArray *copAry;
// 直接崩溃测试
- (void)testCash {
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
self.copAry = arr;
[self.copAry removeObject:@1]; // 直接崩溃
NSLog(@"self.copyAry = %@", self.copAry);
}
报错reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'
那么为什么使用 strong 来修饰就不会有这个问题呢。
我们来看一个例子:
-
定义一个
CJPerson
类,.m 中先不实现setterr
方法
在控制器看一下打印结果
NSMutableArray *names = [@[@"zhangsan"] mutableCopy];
CJPerson *person = [[CJPerson alloc] init];
person.sAry = names;// strong
person.cAry = names;// copy
[names addObject:@"lisi"];
NSLog(@"sAry = %@, cAry = %@", person.sAry, person.cAry);
/* 输出结果:
sAry = (
zhangsan,
lisi
), cAry = (
zhangsan
)
*/
- 归根结底之所以出现这样问题,那是因为
ARC
情况下strong
和copy
对属性setter
方法重写的区别。
strong:setter
时调用了[sAry retain]
方法,实现了指针拷贝,也就是浅拷贝。
copy:setter
时调用了[cAry copy]
方法,实现了内容拷贝,也就是深拷贝。
也就是在 .m 文件中系统默认帮我们实现的是:
-
具体可以看内存地址图详解:(简单粗糙,欢迎大神指正)
2.那么这个与 copy
修饰 NSMutableArray
导致崩溃有什么关系呢?
原来不管是集合类对象(NSArray,NSDictionary,NSSet...),还是非集合类对象(NSString),接收到copy或者mutableCopy消息时,都需遵循以下准则:
-
copy
返回的都是不可变对象,所以如果对copy
返回值去调用可变对象的接口就会 crash - mutableCopy 返回的都是可变对象
所以在- (void)testCash
方法中执行到self.copAry = arr;
ARC 环境下setter
方法执行了copy
方法,导致原本NSMutableArray
类型数组变成NSArray
类型,在调用removeObject:
方法时,自然会出现这个错误reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'
3.那么 NSArray
类型为什么需要使用 copy
来修饰
- 我们继续使用
copy
和strong
来定义变量
@property (strong, nonatomic) NSArray *arr1;
@property (copy, nonatomic) NSArray *arr2;
// 为什么 NSArray 需要使用 copy
- (void)testUserCopyWithAry {
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@(1), @(2), @(3), nil];
self.arr1 = arr;
self.arr2 = arr;
[arr addObject:@(4)];
NSLog(@"arr1 = %@, arr2 = %@", self.arr1, self.arr2);
/* 输出结果:
arr1 = (
1,
2,
3,
4
), arr2 = (
1,
2,
3
)
*/
}
简直白的说就是:如果定义一个数组,使用 strong
来修饰的话,如果这个数组在外界被修改的话,那么这个用 strong
修改的数组变量的值也会跟着变化。为什么?还是因为 strong
进行了指针拷贝。在内存中,两个变量指向的是同一块内存地址。所以为了避免值在外接发生改变而影响自身值的变化,我们通常选择使用 copy
进行修饰。
4.接着再上两个测试例子,对比看输出结果
- (void)test01 {
NSArray *array = @[@1,@2,@3,@4];
NSArray *copyArr = [array copy];
NSArray *mCopyArr = [array mutableCopy];
NSMutableArray *mcArr = [array copy];
NSMutableArray *mmCopyArr = [array mutableCopy];
NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
/* 输出结果
array: 0x60000024ce10
copyArr: 0x60000024ce10
mCopyArr: 0x60000024cd80
mcArr: 0x60000024ce10
mmCopyArr: 0x60000024ce70
*/
}
- (void)test02 {
NSArray *tarray = @[@1,@2,@3,@4];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObjectsFromArray:tarray];
NSArray *copyArr = [array copy];
NSArray *mCopyArr = [array mutableCopy];
NSMutableArray *mcArr = [array copy];
NSMutableArray *mmCopyArr = [array mutableCopy];
NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
/* 输出结果
array: 0x60000024cd20
copyArr: 0x60000024cc90
mCopyArr: 0x60000024ce40
mcArr: 0x60000024cde0
mmCopyArr: 0x60000024d020
*/
}
小结一下:
- NSArray的copy ---->指针拷贝---->浅拷贝
- NSArray的mutableCopy,NSMutableArray的copy, NSMutableArray的mutableCopy 均为深拷贝,即内容拷贝。
- 关于NSString(非集合类对象),NSDictionary及其对应的可变类型都可以此类推。
5.浅拷贝、深拷贝
全篇在讲浅拷贝、深拷贝,追究他们追究到底是什么。
段子手的理解:浅复制好比你和你的影子,你完蛋,你的影子也完蛋。深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。
浅拷贝
- 浅拷贝就是对内存地址的复制,目标对象和原对象指向同一片内存地址。
注意
多个对象共用一块地址时,当内存销毁的时候,指向这片内存地址的几个指针需要重新定义才可以使用,否则出现野指针现象。
在 iOS的浅拷贝 中,通常会使用retain
关键字进行引用计数。因为他既可以让几个指针共同指向同一内存地址,也可以在release
的时候 由于计数的存在,不会让内存销毁,从而出现野指针的现象。
深拷贝
- 深拷贝也就是内容拷贝。目标对象虽然和原对象的值一样,但是所指向的内存地址不一样。可以说深拷贝把原对象地址也拷贝了,而内存地址是自主分配的。因内存地址不一样,两个对象也就互不影响、互不干涉了。
在 iOS的深拷贝 中,通常会使用copy
和mutableCopy
方法
// 深拷贝
- (void)testDeepCopy {
NSString *str = @"abcdefg";
NSString *cStr = [str copy];
NSMutableString *mStr = [str mutableCopy];
[mStr appendString:@"!!"];
NSLog(@"\n str = %@ = %p,\n cStr = %@ = %p,\n mStr = %@ = %p", str, str, cStr, cStr, mStr, mStr);
}
/* 输出结果:
str = abcdefg = 0x109dd8090,
cStr = abcdefg = 0x109dd8090,
mStr = abcdefg!! = 0x604000057e50
*/
再次验证了第 4 个模块
番外篇
浅拷贝的 retain
和 深拷贝中提到的 copy
有什么区别呢
可以观看这篇文章:copy 和 retain 到底有啥区别
6.拷贝构造
当然在 iOS
中并不是所有的对象都支持copy
,mutableCopy
,遵守 NSCopying
协议的类可以发送 copy
消息,遵守NSMutableCopying
协议的类才可以发送 mutableCopy
消息。
假如发送了一个没有遵守上诉两协议而发送 copy
或者 mutableCopy
, 那么就会发生异常。但是默认的 iOS
类并没有遵守这两个协议。如果想自定义一下 copy
那么就必须遵守 NSCopying
, 并且实现 copyWithZone:
方法,如果想自定义一下mutableCopy
那么就必须遵守 NSMutableCopying
, 并且实现 mutableCopyWithZone:
方法。
- 自定义 model,遵守
NSCopying
和NSMutableCopying
协议
@interface CJObj : NSObject<NSCopying, NSMutableCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *icon;
@end
-
实现:
在
ViewController
中打印结果
// 拷贝构造
- (void)copyConstruct {
CJObj *obj = [[CJObj alloc] init];
obj.name = @"zhangsan";
obj.icon = @"icon";
NSMutableArray *arr = [NSMutableArray array];
NSMutableArray *copyAry = [NSMutableArray array];
[arr addObject:obj];
[copyAry addObject:[arr[0] copy]];
//[copyAry addObject:[arr[0] mutableCopy]];
CJObj *obj2 = arr[0];
obj2.name = @"lisi";
obj2.icon = @"obj2_icon";
NSLog(@"arr.name = %@, arr.icon = %@", ((CJObj *)arr[0]).name, ((CJObj *)arr[0]).icon);
NSLog(@"copyArr.name = %@, copyArr.icon = %@", ((CJObj *)[copyAry objectAtIndex:0]).name, ((CJObj *)[copyAry objectAtIndex:0]).icon);
/* 输出结果:
arr.name = lisi, arr.icon = obj2_icon
copyArr.name = zhangsan, copyArr.icon = icon
*/
}
-
内存地址图详解:(简单粗糙,欢迎大神指正)
以上内容在 demo 中均有体现
感谢:
相关阅读: