以数组为例,来记录一下Copy 和 mutableCopy的使用细节。
我们知道可变数组和不可变数组之间的转化可以通过下面的方式:
[NSMutableArray copy] --> NSArray
[NSArray mutableCopy] --> NSMutableArray
我们也知道:
那么当我们调用数组的mutableCopy方法时,是否将数组中所包含的对应也深拷贝了呢?
testClass* class0 = [testClass new];
class0.name = @(1).stringValue;
testClass* class1 = [testClass new];
class1.name = @(11).stringValue;
testClass* class2 = [testClass new];
class2.name = @(111).stringValue;
NSArray* arr = @[class0,class1,class2];
NSMutableArray* arr2 = [arr mutableCopy];
NSArray* arr3 = [arr copy];
看一下结果:
可以看到不论是调用数组的copy还是mutableCopy,数组中的对象指针仍然是指向了最初创建的对象(三个数组中对应对象的地址完全相同)。
经过mutableCopy的数组,生成了一个新的数组arr2,其内存地址和arr不同,是深拷贝。
经过copy的数组,只是生成了一个新的数组对象指针,指向了原始的arr对象,是浅拷贝。
所以可以得出一个结论:对数组进行copy或mutableCopy操作只是对数组这个容器对象进行了深/浅拷贝,而不会对数组中的对象进行操作,最后拷贝完得到的数组中的对象仍旧是指向之前的地址。
这样就会导致,我们在修改经过深拷贝之后的数组中的对象中属性时,同时会影响到原始数组中的对象,达不到想要的结果。
我们在上面testClass对象中遵守
-(id)mutableCopyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
return test;
}
-(id)copyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
return test;
}
然后在这两个地方打个断点,可以发现,在包含该对象的数组进行mutableCopy深拷贝的时候,该对象的copy协议方法并未被调用,这也进一步证实了上面的结论。
1.那么我们想要得到一个数组内对象也经过深拷贝的数组要如何操作呢?
首先当然是在自定义的对象中遵守
然后需要调用:
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;
//其他容器对象 如字典集合等也都有这样类似的方法
当copyItems参数为YES时,会向数组中每个对象发送copy消息,用拷贝好的对象创建新的数组。
NSMutableArray* arr4 = [[NSMutableArray alloc]initWithArray:arr copyItems:YES];
这样就得到了合适的新数组。
2.那么当自定义的对象的属性中有其他自定义对象,该如何处理呢?
首先很自然想到的就是在两个自定义类中都遵守
- initWithArray: copyItems: 方法,我们来看一下能否成功:
@interface testClass : NSObject
@property(nonatomic, strong) NSString* name;
@property(nonatomic, strong) testOther* other;
@end
@implementation testClass
-(id)mutableCopyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
test.other = _other;
return test;
}
-(id)copyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
test.other = _other;
return test;
}
----------
@interface testOther : NSObject
@property(nonatomic,strong) NSString* title;
@end
@implementation testOther
-(id)copyWithZone:(NSZone *)zone{
testOther* other = [[self class]allocWithZone:zone];
other.title = _title;
return other;
}
@end
testOther* other1 = [testOther new];
other1.title = @"o1";
testOther* other2 = [testOther new];
other2.title = @"o2";
testOther* other3 = [testOther new];
other3.title = @"o3";
testClass* class0 = [testClass new];
class0.name = @(1).stringValue;
class0.other = other1;
testClass* class1 = [testClass new];
class1.name = @(11).stringValue;
class1.other = other2;
testClass* class2 = [testClass new];
class2.name = @(111).stringValue;
class2.other = other3;
NSArray* arr = @[class0,class1,class2];
NSMutableArray* arr2 = [[NSMutableArray alloc]initWithArray:arr copyItems:YES];
可以看到虽然testClass类已经深拷贝了,但是testOther类依旧是同样的地址。
我们需要这样处理:
需要在包含其他对象属性的类的copywithzone方法中,创建一个新的testOther对象,然后赋值给当前拷贝的对象的相关指针:
@implementation testClass
-(id)mutableCopyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
testOther* temp = [testOther new];
temp.title = _other.title;
test.other = temp;
return test;
}
-(id)copyWithZone:(NSZone *)zone{
testClass* test = [[self class]allocWithZone:zone];
test.name = _name;
testOther* temp = [testOther new];
temp.title = _other.title;
test.other = temp;
return test;
}
@end
然后再调用- initWithArray: copyItems: 方法。
搞定。
等等,那么自定义对象中包含数组对象呢?
给个提示: 还是这个方法,在哪里用应该挺清楚的了吧...
(ps.感觉要开始套娃了)
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;
//其他容器对象 如字典集合等也都有这样类似的方法