本文源自这里, Demo代码参考这里的Effective-ObjectiveC
准备
后面的例子中用到的两个模型类定义如下
第一个类Conferee
// Conferee.h
@interface Conferee : NSObject
@property (nonatomic, copy) NSString *name;
@end
// Conferee.m
@implementation Conferee
@end
第二个类Conference
// Conference.h
@interface Conference : NSObject <NSCopying>
@property (nonatomic, copy) NSString *conferenceID;
@property (nonatomic, strong) NSMutableArray *conferees;
- (id)shallowCopy;
- (id)deepCopy;
@end
// Conference.m
@implementation Conference
#pragma mark - Custom Accessors
- (NSMutableArray *)conferees {
if (!_conferees) {
_conferees = [NSMutableArray array];
}
return _conferees;
}
#pragma mark - Public
- (id)shallowCopy {
Conference *conference = [[Conference alloc] init];
conference.conferenceID = [self.conferenceID copy];
conference.conferees = [self.conferees mutableCopy];
return conference;
}
- (id)deepCopy {
Conference *conference = [[Conference alloc] init];
conference.conferenceID = [self.conferenceID copy];
conference.conferees = [[NSMutableArray alloc] initWithArray:self.conferees copyItems:YES];
return conference;
}
#pragma mark - NSCopying
- (id)copyWithZone:(nullable NSZone *)zone {
Conference *conference = [[[self class] allocWithZone:zone] init];
conference.conferenceID = [self.conferenceID copy];
conference.conferees = [self.conferees mutableCopy];
return conference;
}
@end
PS: 简要的说明下上面的两个类: Conference会议类里包含了Conferee的数组
引入
在iOS开发中, 经常会遇到拷贝对象的问题
通常都是通过实现NSCopying协议如下的唯一方法来达到对象拷贝的效果
- (id)copyWithZone:(nullable NSZone *)zone;
例如上面的模型类Conferee就实现了该协议
所以可以对Conferee对象进行copy如下
Conferee *conferee = [[Conferee alloc] init];
Conferee *confereeCopy = [Conferee copy];
这里的confereeCopy对象就是conferee对象的拷贝, 即指向的内存地址不同
if (conferee == confereeCopy) {
NSLog(@"conferee[%p] == confereeCopy[%p]", conferee, confereeCopy);
} else {
NSLog(@"conferee[%p] != confereeCopy[%p]", conferee, confereeCopy);
}
打印结果如下
conferee[0x12de83eb0] != confereeCopy[0x1000555f0]
PS: 后面为了简略, 相等时只输出"==", 不等时只输出"!="
不过细心的你, 可能会想到:
"除了NSCopying, 好像一个叫做NSMutableCopying的协议吧? 他俩神马关系?"
既然你想到了, 那我们就把上面的copy方法改成mutableCopy来看看效果吧
Conferee *conferee = [[Conferee alloc] init];
Conferee *confereeMutableCopy = [Conferee mutableCopy];
if (conferee == confereeMutableCopy) {
NSLog(@"conferee[%p] == confereeMutableCopy[%p]", conferee, confereeMutableCopy);
} else {
NSLog(@"conferee[%p] != confereeMutableCopy[%p]", conferee, confereeMutableCopy);
}
打印结果如下
!=
看来两者没什么太大区别么?!
别急, 现在开始我们开始认真推敲推敲敲敲敲...
NSCopying & NSMutableCopying
我们先从字面上来解释下两者的区别
- NSCopying是指不可变复制
- NSMutableCopying是指可变复制
好吧, 这个解释果然很字面!
那我们再看看使用的区别
他们对将要复制的对象(简称receiver)的要求不同
- NSCopying要求recevier必须实现该协议的copyWithZone方法
- NSMutableCopying要求recevier必须实现该协议的mutableCopyWithZone方法
好吧, 又解释了一下, 但是这个又有什么用? 还是不理解好吧!
别急, 要耐住性子学会"慢慢深入"
** 如果receiver是不可变容器 **
- NSCopying返回receiver, 并且receiver的引用计数+1
- NSMutableCopying由receiver中的数据构造一个新的可变实例
** 如果receiver是可变容器 **
- NSCopying由receiver中的数据构造一个新的不可变实例
- NSMutableCopying由receiver中的数据构造一个新的可变实例
有点绕口, 还是不好理解啊?!
好吧, 我们来看下面的例子
** 下述测试用例的准备条件 **
NSArray *array = [NSArray array];
NSMutableArray *mutableArray = [NSMutableArray array];
** 测试用例1: send copy message to array **
NSArray *arrayCopy = [array copy];
if (array == arrayCopy) {
NSLog(@"array[%p] == arrayCopy[%p]", array, arrayCopy);
} else {
NSLog(@"array[%p] != arrayCopy[%p]", array, arrayCopy);
}
if ([arrayCopy isKindOfClass:[NSMutableArray class]]) {
[(NSMutableArray *)arrayCopy addObject:@"addedObject"];
}
if (arrayCopy.count == 0) {
NSLog(@"arrayCopy.count == 0");
} else {
NSLog(@"arrayCopy.count != 0");
}
按照上面所述的原则, arrayCopy和array对象是否相同, arrayCopy.count又是否等于0呢?
为了表述的全面, 先把其他的测试用例也先贴出来, 答案在测试用例4的后面
** 测试用例2: send copy message to mutableArray **
NSArray *arrayCopy = [array copy];
if (array == arrayCopy) {
NSLog(@"array[%p] == arrayCopy[%p]", array, arrayCopy);
} else {
NSLog(@"array[%p] != arrayCopy[%p]", array, arrayCopy);
}
if ([arrayCopy isKindOfClass:[NSMutableArray class]]) {
[(NSMutableArray *)arrayCopy addObject:@"addedObject"];
}
if (arrayCopy.count == 0) {
NSLog(@"arrayCopy.count == 0");
} else {
NSLog(@"arrayCopy.count != 0");
}
** 测试用例3: send copy message to mutableArray **
NSArray *arrayMutableCopy = [array mutableCopy];
if (array == arrayMutableCopy) {
NSLog(@"array[%p] == arrayMutableCopy[%p]", array, arrayMutableCopy);
} else {
NSLog(@"array[%p] != arrayMutableCopy[%p]", array, arrayMutableCopy);
}
if ([arrayMutableCopy isKindOfClass:[NSMutableArray class]]) {
[(NSMutableArray *)arrayMutableCopy addObject:@"addedObject"];
}
if (arrayMutableCopy.count == 0) {
NSLog(@"arrayMutableCopy.count == 0");
} else {
NSLog(@"arrayMutableCopy.count != 0");
}
** 测试用例4: send mutableCopy message to mutableArray **
NSArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
if (mutableArray == mutableArrayMutableCopy) {
NSLog(@"mutableArray[%p] == arrayMutableCopy[%p]", mutableArray, mutableArrayMutableCopy);
} else {
NSLog(@"mutableArray[%p] != arrayMutableCopy[%p]", mutableArray, mutableArrayMutableCopy);
}
if ([mutableArrayMutableCopy isKindOfClass:[NSMutableArray class]]) {
[(NSMutableArray *)mutableArrayMutableCopy addObject:@"addedObject"];
}
if (mutableArrayMutableCopy.count == 0) {
NSLog(@"mutableArrayMutableCopy.count == 0");
} else {
NSLog(@"mutableArrayMutableCopy.count != 0");
}
答案在这里:
- 测试用例1: 对象==, count == 0
- 测试用例2: 对象!=, count == 0
- 测试用例3: 对象!=, count != 0
- 测试用例4: 对象!=, count != 0
PS: 注意这里的count的值的区别, 说明了返回的到底是不可变的array还是可变的array
Shallow Copy & Deep Copy
上述所谓的对象是否相同, 其实就是下面要重点讨论的Shallow Copy和Deep Copy
Shallow Copy(又称浅拷贝)简单地表达就是: ** 只是引用计数+1 **
Deep Copy(又称深拷贝)简单地表达就是: ** 复制了一个全新的对象 **
按照这样的评判标准, 那么对于上述测试用例来说
- 测试用例1: Shallow Copy
- 测试用例2: Deep Copy
- 测试用例3: Deep Copy
- 测试用例4: Deep Copy
至此, 问题还是很好理解的, 那么难度总是要升级的是不是
** 下述测试用例的准备条件 **
Conferee *conferee = [[Conferee alloc] init];
conferee.name = @"yuan";
Conference *conference = [[Conference alloc] init];
conference.conferenceID = @"ID_001";
[conference.conferees addObject:conferee];
** 测试用例5: send copy message to conference **
Conference *conferenceCopy = [conference copy];
if (conference == conferenceCopy) {
NSLog(@"conference[%p] == conferenceCopy[%p]", conference, conferenceCopy);
} else {
NSLog(@"conference[%p] != conferenceCopy[%p]", conference, conferenceCopy);
}
if ([conference.conferees firstObject] == [conferenceCopy.conferees firstObject]) {
NSLog(@"[conference.conferees firstObject][%p] == [conferenceCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceCopy.conferees firstObject]);
} else {
NSLog(@"[conference.conferees firstObject][%p] != [conferenceCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceCopy.conferees firstObject]);
}
测试用里有两个判断
第一个判断是指conferenceCopy对象和原来的conference对象是否相同
第二个判断是指conferenceCopy里成员数组里的conferee对象和原来的conferee对象是否相同
按照惯例, 还是预留给亲爱的您一些独立思考的时间, 答案在测试用例7的下面
** 测试用例6: send shallow copy message to conference **
Conference *conferenceShallowCopy = [conference shallowCopy];
if (conference == conferenceShallowCopy) {
NSLog(@"conference[%p] == conferenceShallowCopy[%p]", conference, conferenceShallowCopy);
} else {
NSLog(@"conference[%p] != conferenceShallowCopy[%p]", conference, conferenceShallowCopy);
}
if ([conference.conferees firstObject] == [conferenceShallowCopy.conferees firstObject]) {
NSLog(@"[conference.conferees firstObject][%p] == [conferenceShallowCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceShallowCopy.conferees firstObject]);
} else {
NSLog(@"[conference.conferees firstObject][%p] != [conferenceShallowCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceShallowCopy.conferees firstObject]);
}
** 测试用例7: send deep copy message to conference **
Conference *conferenceDeepCopy = [conference deepCopy];
if (conference == conferenceDeepCopy) {
NSLog(@"conference[%p] == conferenceDeepCopy[%p]", conference, conferenceDeepCopy);
} else {
NSLog(@"conference[%p] != conferenceDeepCopy[%p]", conference, conferenceDeepCopy);
}
if ([conference.conferees firstObject] == [conferenceDeepCopy.conferees firstObject]) {
NSLog(@"[conference.conferees firstObject][%p] == [conferenceDeepCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceDeepCopy.conferees firstObject]);
} else {
NSLog(@"[conference.conferees firstObject][%p] != conferenceDeepCopy.conferees firstObject][%p]", [conference.conferees firstObject], [conferenceDeepCopy.conferees firstObject]);
}
答案在这里:
- 测试用例5: 对象!=, 数组里的成员对象==
- 测试用例6: 对象!=, 数组里的成员对象==
- 测试用例7: 对象!=, 数组里的成员对象!=
看到这里, 聪明的你或许会有两个疑问
** 疑问1: 为什么conferenceCopy是Deep Copy了? 不是说好的Shallow Copy么?(详见测试用例5) **
因为这里重新实现conference的initWithZone方法如下
Conference *conference = [[[self class] allocWithZone:zone] init];
conference.conferenceID = [self.conferenceID copy];
conference.conferees = [self.conferees mutableCopy];
return conference;
** 疑问2: conference对象的成员数组是Deep Copy, 但是数组里的成员却还是和原来的成员相同(详见测试用例6, 7) **
这是因为数组的拷贝只是单单对于数组对象本身而言
而数组的成员对象默认仍然是浅拷贝的
Shallow Copy和Deep Copy的效果示意如下
effective-objectivec_01.png为了实现数组里成员的Deep Copy, 需要实现如下两个部分
- 1: 使用数组的Deep Copy方法
conference.conferees = [[NSMutableArray alloc] initWithArray:self.conferees copyItems:YES];
- 2: 为成员对象的类实现NSCopying协议的方法
- (id)copyWithZone:(NSZone *)zone {
Conferee *conferee = [[[self class] allocWithZone:zone] init];
conferee.name = self.name;
return conferee;
}
测试用例7就是实现对conference对象的完全Deep Copy(conference.conferees数组里的成员对象也都是深拷贝)
小结
对于Objective-C里的对象拷贝和深浅拷贝的讨论, 今天到此就告一段落了
如果你觉得有什么不好理解或补充, 欢迎conact me
最后还是祝大家iOS开发轻松+愉快
更多文章, 请支持我的个人博客
网友评论
Conference *conference = [[[self class] allocWithZone:zone] init];
在实现copyWithZone:方法里,实例化自己的时候,为什么要用allocWithZone: ,直接alloc init 不行吗?
谢谢楼主的分享,👍