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

前言

大家好,我是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时产生的副本,也对应了上面这句结论。


ios开发基础学习笔记(十)--copy详解_第1张图片
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当作深拷贝,在这里我提醒大家,不是这么回事的!

ios开发基础学习笔记(十)--copy详解_第2张图片
浅拷贝和深拷贝的区别.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详解)