深拷贝与浅拷贝
简单点理解,深复制,在内存中拷贝一份新的对象。 浅复制,没有拷贝新的对象,只是一个地址的引用。 在赋值过程中深复制操作,原对象的引用计数不会增加,浅复制引用计数会加一。 以下是拷贝相关协议:
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
一个对象实例进行 copy 、 mutable 操作得到什么结果,取决于该实例类型对以上方法的具体实现
集合类型 copy 操作和 mutableCopy 操作
一个类遵循 NSCopying, NSMutableCopying 协议并且实现相对应的初始化方法后,这个类就具有对象拷贝的能力。如果你自定义类没有遵守协议直接调用 copy / mutableCopy 程序会奔溃。拷贝协议的具体使用我这里不做扩展感兴趣的可以自行 Google 一下。有一种观点:copy 操作就是浅复制,mutableCopy 就是深复制,这是一个非常错误的理解。以下是我的一些总结:
- 可变的集合对象 + copy 得到一个新的对象(新对象不可变) 深复制
- 可变的集合对象 + mutablecopy 得到一个新的对象(新对象可变) 深复制
- 不可变集合对象 + copy 没有得到新的对象(地址的引用) 浅复制
- 不可变集合对象 + mutablecopy 得到一个新的对象(新对象可变) 深复制
OC 中集合对象 NSArray、NSMutableArray、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet等,另外 NSString、NSMutableString 也准守以上原则。实践出真理我们来看看代码中的效果:
NSString *str = @"abc";
NSMutableString *mStr = [NSMutableString stringWithString:str];
NSString *strCopy = [str copy];
NSString *strMutableCopy = [str mutableCopy];
NSString *mStrCopy = [mStr copy];
NSString *mStrMutableCopy = [mStr mutableCopy];
NSArray *arr = @[@"abc"];
NSMutableArray *mArr = [NSMutableArray arrayWithArray:arr];
NSArray *arrCopy = [arr copy];
NSArray *arrMutableCopy = [arr mutableCopy];
NSArray *mArrCopy = [mArr copy];
NSArray *mArrMutableCopy = [mArr mutableCopy];
NSDictionary *dic = @{@"1":@"abc"};
NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithDictionary:dic];
NSDictionary *dicCopy = [dic copy];
NSDictionary *dicMutableCopy = [dic mutableCopy];
NSDictionary *mDicCopy = [mDic copy];
NSDictionary *mDicMutableCopy = [mDic mutableCopy];
NSSet *set = [NSSet setWithObject:@"1"];
NSMutableSet *mSet = [NSMutableSet setWithSet:set];
NSDictionary *setCopy = [set copy];
NSDictionary *setMutableCopy = [set mutableCopy];
NSDictionary *mSetCopy = [mDic copy];
NSDictionary *mSetMutableCopy = [mDic mutableCopy];
我们选取 NSArray、NSMutableArray来看一下 copy 和 mutableCopy 的结果
NSMutableArray 属性请使用 strong 修饰符
数学里面一个证明方式叫做反证法,接下来我们自定义一个类,增加一个NSMutableArray属性,使用copy来修饰
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
@property (nonatomic, copy) NSMutableArray *mutableArr;
@end
#import "TestObject.h"
@implementation TestObject
- (instancetype)init{
self = [super init];
// self.mutableArr = [NSMutableArray arrayWithObject:@"1"];
_mutableArr = [NSMutableArray arrayWithObject:@"1"];
[self.mutableArr addObject:@"2"];//这一步,如果使用点语法进行属性赋值,程序crash;如果直接使用成员变量方式赋值,代码没有问题
return self;
}
//一般情况编译器会自动给属性添加get/set方法
- (void)setMutableArr:(NSMutableArray *)mutableArr{
_mutableArr = [mutableArr copy];
}
@end
执行下面代码
TestObject *testObject = [TestObject new];
testObject.mutableArr = [NSMutableArray arrayWithObject:@"1"];
[testObject.mutableArr addObject:@"2"];
程序crash,控制台提示错误 [__NSSingleObjectArrayI addObject:]: unrecognized selector sent to instance 0x1c0017730 这一步我们就证明了如果我们使用 copy 来修饰一个可变的数组属性,然后通过点语法的方式把一个可变的数组赋值给该属性,最后该属性指向的是一个拷贝得到的全新的不可变数组,这个时候我们可以在代码中调用 arrayWithObject 等改变数组的 API(因为 @property (nonatomic, copy) NSMutableArray *mutableArr 告诉编译器这是一个可变的数组),但是在运行时,这个属性指向的是一个不可变的数组,引发 unrecognized selector sent to instance 错误,从而导致 crash。
NSArray 属性请使用 copy 修饰符
我们定义一个 NSArray 类型的属性,目的是为了在初始化或者赋值操作后,这个数组集合就不能够改变的(增加或者删除元素等)。如果我们使用 strong去修饰 NSArray 属性,并且我们把一个可变的数组 NSMutableArray 对象赋值给了该属性(父类的指针可以指向子类对象),strong 并不会在内存中拷贝新的对象,只是一个地址的引用,所以NSArray类型的属性实质上指向的是一个 NSMutableArray,这个时候如果其他地方还有一个 NSMutableArray 指针变量指向这个 NSMutableArray 对象,并且通过这个变量改变了数组,也会造成NSArray类型属性的改变,因为它们指向是一个地址内存,也就是一个对象。这就违背了我们设计的初衷,同时可能引发一些业务逻辑问题。
尾语
以上内容有任何的疑问、错误,请各路大神指出,同时希望能够帮助到大家