美文网首页iOS 开发 iOS Developer
iOS开发 之 对象拷贝

iOS开发 之 对象拷贝

作者: 诺之林 | 来源:发表于2016-10-12 14:25 被阅读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

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

    • 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开发轻松+愉快

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

    相关文章

      网友评论

      • KorsJJ:你的那几个实例是不是写反了呀?
      • MoussyL:想问下楼主:
        Conference *conference = [[[self class] allocWithZone:zone] init];
        在实现copyWithZone:方法里,实例化自己的时候,为什么要用allocWithZone: ,直接alloc init 不行吗?
        谢谢楼主的分享,👍
        MoussyL:@提督很忙啊 说的对 ~:+1:
        山林间迷雾能不能当障眼法的内容:可以的 alloc 最终也是调用allocwithZone 你说呢 既然能拿到zone了 为何不allocwithzone 呢

      本文标题:iOS开发 之 对象拷贝

      本文链接:https://www.haomeiwen.com/subject/arsiyttx.html