美文网首页IOS开发基础(oc)
ios开发基础学习笔记(十)--copy详解

ios开发基础学习笔记(十)--copy详解

作者: miloluo | 来源:发表于2018-06-03 17:53 被阅读0次

    前言

    大家好,我是milo。本篇文章主要围绕着copy这一关键字讲解,包括copy和mutableCopy的区别、浅拷贝和深拷贝的区别、@property的copy、自定义类实现copy、继承父类实现copy的讲解。我将通过先给出结论再进行验证的方式说明,避免大家凌乱混淆。

    copy和mutableCopy的区别

    如何使用copy功能

    一个对象可以调用copy或mutableCopy方法来创建一个副本对象。如下面一个简单的示范:

        NSString *str1 = @"hello";
        NSString *str2 = [str1 copy];
        NSLog(@"%@ , %@",str1,str2);//    打印 “hello , hello”
    

    非常简单,像对象方法一样调用就可以使用copy功能。

    但是我们需要注意的是:
    copy : 创建的是不可变副本(如NSString、NSArray、NSDictionary)
    mutableCopy :创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary)

    一句话作为结论:copy生成的是不可变对象,mutableCopy生成的是可变对象

    下面这张图是不同源对象调用copy或mutableCopy时产生的副本,也对应了上面这句结论。


    copy和mutableCopy生成的副本图.png

    下面我们将对上图进行验证,如果结果如上图所示那样,那么结论便是正确的,即:copy生成的是不可变对象,mutableCopy生成的是可变对象

    虽说结论是最重要的,但是我也推荐大家花个几分钟看完验证过程,才会真正理解结论。由于NSString、NSArray、NSDictionary得出的结论相同,我就只对NSString进行验证,以下为验证过程:

    NSString调用Copy:

        NSString *str1 = @"hello";
        NSMutableString *str2 = [str1 copy];
    //将copy生成的对象赋值给NSMutableString,再对其进行修改,如果报错,说明生成的是不可变对象
        [str2 appendString:@"world"];//这里报错了,说明生成的是不可变对象
        NSLog(@"%@ , %@",str1,str2);
    

    同样地,将上段代码的copy改为mutableCopy进行验证,如下:

    NSString调用mutableCopy:

        NSString *str1 = @"hello";
        NSMutableString *str2 = [str1 mutableCopy];
    //将copy生成的对象赋值给NSMutableString,再对其进行修改,如果报错,说明生成的是不可变对象
        [str2 appendString:@"world"];//这里没有报错,说明生成的是可变对象
        NSLog(@"%@ , %@",str1,str2);// 打印 “hello , helloworld”
    

    综合NSString调用拷贝方法情况来看,NSString通过copy产生的实际是NSString,如果把NSString赋值给NSMutableString指针并且修改它,就会报错;而通过mutableCopy产生的实际是NSMutableString,就可以进行修改,虽然赋值给NSString指针不会错,但是就变成了不可变的了,那使用mutableCopy就没意义了,所以要避免。

    同样的方法,再对NSMutableString进行copy和mutableCopy调用。
    NSMutableString调用copy

        NSMutableString *str1 = [NSMutableString stringWithString:@"hello"];
        NSMutableString *str2 = [str1 copy];
    //将copy生成的对象赋值给NSMutableString,再对其进行修改,如果报错,说明生成的是不可变对象
        [str2 appendString:@"world"];//这里报错,说明生成的是不可变对象
        NSLog(@"%@ , %@",str1,str2);
    

    NSMutableString调用mutableCopy

        NSMutableString *str1 = [NSMutableString stringWithString:@"hello"];
        NSMutableString *str2 = [str1 mutableCopy];
    //将copy生成的对象赋值给NSMutableString,再对其进行修改,如果报错,说明生成的是不可变对象
        [str2 appendString:@"world"];//这里没有报错,说明生成的是可变对象
        NSLog(@"%@ , %@",str1,str2);// 打印 “hello , helloworld”
    

    综合NSMutableString调用拷贝方法情况来看,和前面的结论也一致,就是无论是谁调用copy生成的都是生成不可变对象,无论谁调用mutableCopy生成的都是可变对象。

    小结:总之,一句话,copy生成的是不可变对象,mutableCopy生成的是可变对象!!

    浅拷贝和深拷贝的区别

    网上很多文章会把copy当作浅拷贝,mutableCopy当作深拷贝,在这里我提醒大家,不是这么回事的!

    浅拷贝和深拷贝的区别.png

    浅拷贝和深拷贝是从内存的角度来进行区分的,如果仅仅以copy和mutableCopy区分,那是不对的,正确区分浅拷贝和深拷贝的结论应该是如下:

    结论:只有源对象和副本对象都不可变时,才是浅拷贝,其它都是深拷贝。

    如NSString通过copy赋值给NString,是浅拷贝,因为都不可变,所以只是指针拷贝(编译器优化)就可以不影响数值了,因为NSString原则上说不可变;而NSString通过mutableCopy赋值给NSMutableString就是深拷贝,需要生成一个完整的副本。

    //    NSString部分
        NSString *str1 = @"hello";
        
        NSString *str2 = [str1 copy];
        NSLog(@"%p,%p",str1,str2);// 地址相同,浅拷贝
        
        NSString *str3 = [str1 mutableCopy];
        NSLog(@"%p,%p",str1,str3);// 地址不相同,深拷贝
        
    //    NSMutableString部分
        NSMutableString *str4 = [NSMutableString stringWithFormat:@"hello"];
        
        NSString *str5 = [str4 copy];
        NSLog(@"%p,%p",str4,str5);// 地址不相同,深拷贝
    
        NSMutableString *str6 = [str4 copy];
        NSLog(@"%p,%p",str4,str6);// 地址不相同,深拷贝
    

    通过上面一段代码,我们就可以得到不同的情况。但都只有一种情况是浅拷贝,即NSString对象拷贝产生NSString对象的时候,此时是指针拷贝。由于编译器会对浅拷贝做优化,所以后面即使有修改,也不会影响彼此,因为编译器让其变成了深拷贝。

    小结即结论:只有源对象和副本对象都不可变时,才是浅拷贝,其它都是深拷贝。

    @property的copy

    当我们在使用@property声明字符串的时候,都是像下面这样声明:

    @property (nonatomic, copy) NSString *name;
    

    其中,为什么要用copy,而不是用retain呢?
    答:因为我们声明的是不可变类型NSString,不希望在赋值以后做其他更改,所以用copy,无论是浅拷贝和深拷贝,都不会受到别人(常指NSMutableString)的影响。而如果用retain,就会在赋值好以后,可能被NSMutableString改掉,因为retain是索引计数+1的声明方式,属于新增一个指针对象指向内存。

    所以结论就是:
    使用copy修饰不可变对象,就可以防治被无意中修改。

    我们可以将copy修改为retain验证下:

        JJPerson *person = [[JJPerson alloc] init];
        NSMutableString *name = [NSMutableString stringWithString:@"milo"];
        [person setName:name];
        NSLog(@"%@",[person name]);//    打印 “milo”
        
        [name appendString:@"&vicky"];
        NSLog(@"%@",[person name]);//    打印 “milo&vikcy”
    

    我们在赋值以后,再对外部的name修改时,[person name] 啥事也没干就被改了(想想你的名字取好了,又被别人无意改了一下,气不气)

    所以我们在声明NSString、NSArray、NSDictionary这些不可变对象的时候,一定要用copy,防止被更改。但当我们想使用NSMutableString、NSMutableArray、NSMutableDictionary这些可变对象的时候,就要对应使用retain了,因为它们就是想要被改!

    小结:
    1、copy修饰不可变对象,就可以防治被无意中修改
    2、retain修饰可变对象,因为它们就是想被改

    自定义类实现copy

    前面都是在以NSString为例举各种关于copy的例子,我们如果想让自定义的类也实现拷贝,就需要:
    1、有一个类
    2、实现相应协议,想用copy,则遵守NSCopying协议,想用mutableCopy,则遵守NSMutableCopying协议
    3、实现协议中的copyWithZone方法或mutableCopyWithZone方法
    4、调用

    除了3需要上段代码看看如何实现以外,其他都不用说了:

    -(id)copyWithZone:(NSZone *)zone {
        JJPerson *person = [[JJPerson alloc] init];
        [person setAge:self.age];
        [person setName:self.name];
        return person;
    }
    

    非常简单对吧!就是把当前属性的值,重新赋值给新对象。而zone参数不用去理会,它是个古老的技术。

    继承父类实现copy

    这一部分跟前面的“自定义类实现copy”部分相关联,就是当子类继承父类时的拷贝实现。

    只需要记住一个结论:
    子类继承父类时,如果有新增属性,那么copy方法就要重写,否则新增属性值就是空的。

    子类实现部分:

    -(id)copyWithZone:(NSZone *)zone {    
        JJStudent *student = [super copyWithZone:zone];
        [student setSId:self.sId];
        return student;
    }
    

    父类也需要做如下改动:

    -(id)copyWithZone:(NSZone *)zone {
    
        // 只能copy出Person对象
        // Person *p = [[Person alloc] init];
    
        // 能够保证copy出 Person 对象以及子类
        Person *p = [[self.class alloc] init];
        [person setAge:self.age];
        [person setName:self.name];
        return person;
    }
    

    相关文章

      网友评论

        本文标题:ios开发基础学习笔记(十)--copy详解

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