一、什么是拷贝
在 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 }
运行结果
总结 : 可见,不可变对象调用 copy 方法不会产生新的对象,如图
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 }
运行结果
总结 : 可变对象调用 copy 是深拷贝,会产生新的对象,如图
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 }
运行结果
总结 : 不可变对象调用 mutableCopy 会产生一个新的对象,如图
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 }
运行结果
总结 : 可变对象调用 mutableCopy 会产生一个新的对象,如图
三、自定义类对象
先定义一个 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 }
运行结果
分析 : main 函数中执行到第 5 行代码的内存图如下
注意,此时 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 实例变量,如图
此时,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: 方法一样,只不过返回的是一个可变对象