通常, 我們在定義 不可變字符串和不可變集合 的時候, 會用到 copy 這個存儲屬性, 那我們為何要用 copy? copy 到底給我們帶來了什么?
要回答這個問題, 首先來了解一下 copy
什么是 copy
如果你有 Objective-C 的編程基礎, 那么你一定知道 Strong(強引用), 和 weak(弱引用), 那么 copy 是什么呢?
總所周知, 你定義一個 strong 或是 weak 屬性的時候, 默認的都會創建以一個下劃線( _ )開頭的同名實例變量, 強弱引用也是和定義的時候保持一致, 那么 copy 呢?
當然, 沒有 copy 這種引用關系, 那到底是什么呢? 我們來實際測試一下
@interface ViewController ()
@property (nonatomic, copy) NSArray *arrayTest;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *array = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
_arrayTest = array;
array = nil;
NSLog(@"%@", _arrayTest.lastObject);
}
@end
上面這段代碼中, 我們直接使用 arrayTest 這個屬性的實例變量, 僅僅只是想確認實例變量的存儲屬性是 strong, weak, 還是 unsafe unretain
可以看到, 如果是 weak, 那么打印出來應該是 (null)
, 如果是 unsafe unretain 那么會崩潰, 如果是 strong, 那么就會正常打印出 2
這個元素
實際結果如何?
看來是強引用.
所以, 用 copy 定義的屬性, 對應的是一個強引用的實例變量
copy 做了什么
copy 對應的 setter 中, 會調用 copy 方法
比如
-(void)setArrayTest:(NSArray *)arrayTest{
_arrayTest = [arrayTest copy];
}
[arrayTest copy]
會調用 copyWithZone:
方法, 后者是 NSCopying
協議的一個方法, 作用就是返回一個自己的拷貝.
需要注意的是, 返回的拷貝對象是不可變的對象, 例如, NSMutableArray
copy 后會返回 NSArray
對象.
另外, 對于不可變對象, copy 其實返回的就是自身, 并沒有拷貝.
下面寫個代碼測試一下
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@"1", @"2", nil];
NSArray *mutableArrayCopy = [mutableArray copy];
[mutableArray addObject:@"3"];
NSLog(@"%d", [mutableArrayCopy isKindOfClass:[NSMutableArray class]]);
NSLog(@"%p, %p", mutableArrayCopy, mutableArray);
NSLog(@"%@, %@", mutableArrayCopy, mutableArray);
NSArray *array = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
NSArray *arrayCopy = [array copy];
NSLog(@"%p, %p", array, arrayCopy);
所以, copy 做的就是針對可變對象, 生成一份不可變對象, 如果本身就是不可變對象, 則返回自己.
為什么要用 copy
使用 copy 主要是為了保證數據一致性.
例如, 想象一下, 你自己寫了一個類似 UILabel 的控件, MyLabel, 里面有一個屬性text, 使用的是 strong 修飾
@property(nonatomic, strong) NSString *text;
在 setter 里面, 為了減少不必要的重繪, 做了一點優化, 自以為完美無缺, 甚至想要給自己點個贊!
-(void)setText:(NSString *)text{
if( _text == text) {
return;
}
_text = text;
[self setNeedsDisplay];
}
但是如果碰到某個奇怪的開發者, 寫上這個代碼
- (void)viewDidLoad {
[super viewDidLoad];
self.titleString =[NSMutableString stringWithString:@"12345"];
self.titleLabel.text = self.titleString;
}
然后在某個事件響應里面寫上
[self.titleString appendString:@"678"];
self.titleLabel.text = self.titleString;
那么界面上永遠也顯示不出來后面加上的這一段字符.
有一個修改辦法就是把 MyLabel 中的 text 屬性改成 copy
然后 setter 中也改成
_text = [text copy];
這樣就搞定了! 既沒有多余的重繪, 也沒有漏掉的重繪! 這次可以點個贊了!