我们在声明 NSStringNSArray等具有可变子类的属性时,一般都会用 copy 关键字来指定它的特质。

为什么要用 copy

copy 关键字的解释是:此特质所表达的所属关系与 strong(为属性设置新值时,先保留新值,并释放旧值,然后再将新值设置上去)类似。不同的是,设置新值时,不保留新值,而是将其“拷贝”。

看上去并没有什么大不了的,拷贝与否好像和设置成功不成功的关系不大!但是,传递给设置方法的新值可能是一个可变类的实例(父类指针指向子类对象),例如,给类型为 NSString 的属性设置新值时,可能会传递一个NSMutableString的实例。此时如果不拷贝字符串,设置完属性后,字符串的值可能会在对象不知道的情况下遭人更改。所以要拷贝一份”不可变”的字符串,确保对象中的字符串不会无意间被改变。

理解copy的作用

举个例子来说:

1
2
3
4
5
6
7
8
@property (nonatomic, strong) NSString *aString;

self.aString = @"test string";
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"hello world"];
self.aString = mutableString;
[mutableString setString:@"hahaha"];
NSLog(@"%@",_aString);
//print: hahaha

我们首先声明了一个不可变字符串类型的属性 aString,并且给它赋了一个值,然后初始化了一个可变的字符串 mutableString,由于OC这门语言的多态性,即父类指针可以指向子类对象,因为 NSMutableStringNSString 的子类,所以,我们可以将不可变字符串类型的指针 self.aString 指向可变字符串类型的对象 mutableString,然后当我们改变 mutableString 的值的时候,此时不可变字符串 self.aString 的值也随之改变,假如我们再其他较多的地方也使用了 self.aString 这个对象,那么该对象的值就会在我们不知情的情况下遭到修改,然后就会造成多处使用错误。
下面我们对上述代码稍作修改:

1
2
3
4
5
6
self.aString = @"test string";
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"hello world"];
self.aString = [mutableString copy];
[mutableString setString:@"hahaha"];
NSLog(@"%@",_aString);
//print: hello world

在将可变字符串赋值给 self.aString 的时候,进行一步copy的操作,此时可以看到打印的信息为 hello world ,这就说明,当我们修改可变对象的时候没对不可变对象造成任何影响。
为了避免每次赋值都进行一次 copy 操作,有的时候也会遗漏,我们可以在声明属性的时候,使用 copy 关键字,copy 关键字的作用就是每当该属性被赋值的时候,都进行一次拷贝,这样就可以保证这个对象永远不会因为其可变子类对象的修改而被修改。例如:

1
2
3
4
5
6
7
8
@property (nonatomic, copy) NSString *aString;

self.aString = @"test string";
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"hello world"];
self.aString = mutableString;
[mutableString setString:@"hahaha"];
NSLog(@"%@",_aString);
//print: hello world

使用 copy 关键字就可以保证该属性指向的值永远为不可变对象。

copy 的原理

那么 copy 关键字怎么保证我们每次辅助的时候都进行一次拷贝的n呢?

假如我们声明一个 strong 修饰的属性:

1
@property (nonatomic, strong) NSString *aString;

那么它被合成的 -setter 方法为:

1
2
3
4
// ARC 下
- (void)setAString:(NSString *)aString {
_aString = aString;
}
1
2
3
4
5
6
7
8
9
// MRC 下
- (void)setAString:(NSString *)aString {
// 释放旧值
[_aString release];
// retain 新值
_aString = [aString retain];
// release 新值
[aString release];
}

MRC 下的 -setter 方法可以让我们清晰的看到 strong 关键字的作用。那么 copystrong 的不同点在哪呢?

如下代码:

1
@property (nonatomic, copy) NSString *aString;

它被合成的 -setter 方法为:

1
2
3
4
// ARC
- (void)setAString:(NSString *)aString {
_aString = [aString copy];
}
1
2
3
4
5
6
7
8
9
// MRC
- (void)setAString:(NSString *)aString {
// 释放旧值
[_aString release];
// 拷贝新值
_aString = [aString copy];
// release 新值
[aString release];
}

不同点在于它会将新值的拷贝设置给属性,而不是直接 retain。这就可以确保该属性持有的那一份一直都是被设置的对象的拷贝,所以无论怎么修改被设置的对象,都不会影响它。