一、什么是拷贝

在 OC 中,可以使用两个方法分别对一个 OC 对象进行拷贝(产生副本),产生的副本和原对象具有相同的内容,这两个方法分别是



- (id)copy;
- (id)mutableCopy;



如果想使用 copy 方法,那么该类必须遵守 <NSCopying> 协议

如果想使用 mutableCopy 方法,那么该类必须遵守 <NSMutableCopying> 协议

 

在看它们的区别之前,我们先来解释一下拷贝的分类

 

拷贝的分类 

  1)浅拷贝 : 即指针拷贝,简单的说就是拷贝指针,不会产生新的对象,原对象的 引用计数 +1

  2)深拷贝 : 会产生一个新的对象,原对象的引用计数不变,新对象的引用计数为 1

 

注意 : 只有不可变对象(NSString、NSArray、NSDictionary等)调用 copy 方法才是浅拷贝,其余都是深拷贝

 

copy 和 mutableCopy 方法的区别

  1)copy : 创建的是一个不可变对象(NSString、NSArray、NSDictionary)

  2)mutableCopy : 创建的是一个可变的对象 (NSMutableString、NSMutableArray、NSMutableDictionary)

 

二、拷贝示例

1)不可变对象调用 copy



1 void test_2() {
 2 
 3     // 引用计数 : 1
 4     NSString * str_1 = [[NSString alloc] initWithFormat:@"age is %i", 10];
 5     
 6     // copy 语法并没有产生一个新的对象,而是指向原来的对象
 7     // 引用计数 : 2
 8     NSString * str_2 = [str_1 copy];
 9     
10     NSLog(@"str_1 --- %p", str_1);
11     NSLog(@"str_2 --- %p", str_2);
12     
13     [str_1 release];
14     
15     [str_1 release];
16     
17 
18 }



 

运行结果

oc语言ios从系统拷贝文件权限_不可变对象

总结 : 可见,不可变对象调用 copy 方法不会产生新的对象,如图

oc语言ios从系统拷贝文件权限_不可变对象_02

 

2)可变对象调用 copy



1 void test_3() {
 2 
 3     NSMutableString * mulString = [NSMutableString stringWithFormat:@"age is %i", 10];
 4     
 5     // 可变对象调用 copy 是深拷贝
 6     NSString * str = [mulString copy];
 7     
 8     NSLog(@"mulString --- %p", mulString);
 9     NSLog(@"str --- %p", str);
10 
11     [mulString release];
12     
13     [str release];
14 
15 }



运行结果

oc语言ios从系统拷贝文件权限_不可变对象_03

总结 : 可变对象调用 copy 是深拷贝,会产生新的对象,如图

oc语言ios从系统拷贝文件权限_实例变量_04

 

3)不可变对象调用 mutableCopy



1 void test_1() {
 2 
 3     // 引用计数 : 1
 4     NSString * str = [[NSString alloc] initWithFormat:@"age is %i", 10];
 5     
 6     // 产生一个新的对象
 7     // 引用计数 : 1
 8     NSMutableString * mulStr = [str mutableCopy];
 9     
10     NSLog(@"str --- %p", str);
11     NSLog(@"mulStr - %p", mulStr);
12     
13     [str release];
14     
15     [mulStr release];
16 
17 }



运行结果

oc语言ios从系统拷贝文件权限_实例变量_05

总结 : 不可变对象调用 mutableCopy 会产生一个新的对象,如图

oc语言ios从系统拷贝文件权限_实例变量_06

 

4)可变对象调用 mutableCopy

 



1 void test_4 () {
 2 
 3     // 引用计数 1
 4     NSMutableString * mulString = [[NSMutableString alloc] initWithFormat:@"age is %i", 10];
 5     
 6     // 可变对象调用 mutableCopy 产生新的对象
 7     // 引用计数 1
 8     NSMutableString * mulString_2 = [mulString mutableCopy];
 9     
10     NSLog(@"mulString --- %p", mulString);
11     NSLog(@"mulString_2 --- %p", mulString_2);
12     
13     [mulString release];
14     
15     [mulString_2 release];
16 
17 }



 

运行结果

oc语言ios从系统拷贝文件权限_实例变量_07

总结 : 可变对象调用 mutableCopy 会产生一个新的对象,如图

oc语言ios从系统拷贝文件权限_oc语言ios从系统拷贝文件权限_08

 

 三、自定义类对象

先定义一个 Student 类,如下



// Student.h
@interface Student : NSObject

// copy 代表 setter 方法 release 旧对象,copy 新对象
@property (nonatomic, copy) NSString * name;

@end

// Student.m
#import "Student.h"

@implementation Student

- (void)dealloc {
    
    NSLog(@"%@ 被释放", _name);

    [_name release];
    
    [super dealloc];

}

@end



其中,name 属性选择 copy 语法,选择 copy 时其自动生成的 setter 方法等价于下面



1 - (void)setName:(NSString *)name {
 2 
 3     if(_name != name) {
 4     
 5         [_name release];
 6         
 7         _name = [name copy];
 8         
 9     }
10 
11 }



在 main 中编写如下代码



1 void test_5() {
 2 
 3     Student * stu = [[Student alloc] init];
 4     
 5     NSMutableString * string = [NSMutableString stringWithFormat:@"age is %i", 10];
 6     
 7     stu.name = string;
 8     
 9     // 此时,改变外部的 string 并不会影响到 stu 内部的  string
10     
11     [string appendString:@"ab"];
12     
13     NSLog(@"string: %@", string);
14     NSLog(@"stu.name: %@", stu.name);
15     
16     [stu release];
17 
18 }



运行结果 

oc语言ios从系统拷贝文件权限_实例变量_09

分析 : main 函数中执行到第 5 行代码的内存图如下

oc语言ios从系统拷贝文件权限_oc语言ios从系统拷贝文件权限_10

 

注意,此时 stu 中的 name 实例变量还未指向任任何对象,所以是 nil

重点在第 6 行,运行此行时,会调用 setName: 方法,并将 string 作为 setName: 的参数

  1)首先判断 stu 的当前 name 实例变量和传入参数的 是否是同一个,显然不是

  2)然后将 stu 的 name 实例变量 指向的对象释放,此时 nil,nil 执行 release 是可以的;如果不是 nil,则当前 name 实例变量指向的对象则会被释放

  3)然后会对传入的参数 name(就是 main 中的 string)调用 copy 方法,这里注意一下,因为我在定义 name 属性的时候,使用的类型是 NSString(不可变),所以 setName: 参数的类型也就是 NSString,但是,在 main 中的 string 是 NSMutableString 类型,因为 NSMutableString 是 NSStirng 的子类,所以是可以传给 setName: 中的形参的;其实,string 和 name 所指的对象都是同一个,且它的类型就是 NSMutableString;所以,对一个 可变对象调用 copy 方法,会产生一个新的对象,然后将这个新的对象的地址赋给 stu 的 name 实例变量,如图

oc语言ios从系统拷贝文件权限_oc语言ios从系统拷贝文件权限_11

此时,string 和 stu.name 所指的对象是不同的对象,修改 string 不会影响到 stu.name 的,所以结果就如上面那样了

 

四、NSCopying 和 NSMutableCopying 协议

若想让一个类的对象支持拷贝,就必须要将该类遵守 NSCopying 或 NSMutableCopying 协议

 

NSCopying 和 NSMutableCopying 协议中都只有一个方法,分别是

 



// NSCopying 协议的方法
- (id)copyWithZone:(nullable NSZone *)zone;

// NSMutableCopying 协议的方法
- (id)mutableCopyWithZone:(nullable NSZone *)zone;



 

 

当一个对象调用 copy 方法时, copy 方法就会自动去调用 copyWithZone: 方法来实现目的

copyWithZone: 方法的实现可以理解为下面的代码

 

 

首先声明一个 Person 类,并声明两个属性 name 和 age,且让 Person 遵循 NSCopying 协议



1 @interface Person : NSObject <NSCopying>
2 
3 @property (nonatomic, copy) NSMutableString * name;
4 
5 @property (nonatomic, assign) int age;
6 
7 @end



 

在 Person.m 文件中实现 copyWithZone: 方法,如下



1 - (id)copyWithZone:(NSZone *)zone {
 2 
 3     // zone 是系统已经分配好为当前对象分配的内存
 4     // 不需要释放,要在外部释放
 5     
 6     Person * person = [[[self class] allocWithZone:zone] init];
 7     
 8     person.name = _name;
 9     person.age = _age;
10     
11     return person;
12 
13 }



总结 :

  1)在该方法中,一定要用 [self class] 来获取当前类,如果 Person 有个子类 Student,那么 Student 对象就会获得当前正确的类,而不是其父类 Person

  2)然后通过 allocWithZone: 方法创建一个指定好的内存空间进行分配,指定的内存空间由参数确定

  3)然后将当前对象的内容赋给新创建的对象

  4)最后返回这个对象,注意,一定要返回的是不可变对象

 

mutableCopyWithZone: 方法和 copyWithZone: 方法一样,只不过返回的是一个可变对象