iOS 开发 iOS Developer

iOS开发 之 对象拷贝

2016-10-12  本文已影响2314人  诺之林

本文源自这里, 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

我们先从字面上来解释下两者的区别

好吧, 这个解释果然很字面!

那我们再看看使用的区别

他们对将要复制的对象(简称receiver)的要求不同

好吧, 又解释了一下, 但是这个又有什么用? 还是不理解好吧!

别急, 要耐住性子学会"慢慢深入"

** 如果receiver是不可变容器 **

** 如果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");
} 

答案在这里:

PS: 注意这里的count的值的区别, 说明了返回的到底是不可变的array还是可变的array

Shallow Copy & Deep Copy

上述所谓的对象是否相同, 其实就是下面要重点讨论的Shallow Copy和Deep Copy

Shallow Copy(又称浅拷贝)简单地表达就是: ** 只是引用计数+1 **

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]);
}

答案在这里:

看到这里, 聪明的你或许会有两个疑问

** 疑问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, 需要实现如下两个部分

conference.conferees = [[NSMutableArray alloc] initWithArray:self.conferees copyItems:YES];
- (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开发轻松+愉快

更多文章, 请支持我的个人博客

上一篇下一篇

猜你喜欢

热点阅读