Copy的作用
- 在OC中,copy 是利用一個源對象產生一個副本對象,本質就是當修改源對象的屬性和行為,不會影響副本對象,同樣,當修改副本對象的屬性和行為,不會影響源對象。
Copy 的使用
- 注意: 使用之前必須要遵守 NSCopying 協議,實現 copyWithZone: 方法
其中有兩點需要注意:
1、Foundation類已經遵守了<NSCopying>和 <NSMutableCopying>協議,即實現了copy和mutableCopy方法,因此Foundation對象可以使用這些方法創建對象的副本或可變副本
,例如: NSString 、 NSDictionary 、NSArray 、 NSMutableArr ...
2、在iOS中并不是所有的對象都支持copy,mutableCopy,遵守NSCopying 協議的類可以發送copy消息,遵守NSMutableCopying 協議的類才可以發送mutableCopy消息。假如發送了一個沒有遵守上訴兩協議而發送 copy或者 mutableCopy,那么就會發生異常。如果想自定義一下copy 那么就必須遵守 NSCopying,并且實現 copyWithZone: 方法,如果想自定義一下mutableCopy 那么就必須遵守NSMutableCopying,并且實現 mutableCopyWithZone: 方法, 總之想要 Copy 或是mutableCopy 必須要實現 copyWithZone: 或是 mutableCopyWithZone: 方法 這兩個方法,協議不簽訂也沒有問題
,只不過不簽訂協議沒有協議方法的代碼提示.
例如: 自定義一個 Person 類,當操作 copy 時,如果沒有實現 NSCopying 協議,那么會出現錯誤。
Person *p1 = [[Person alloc]init];
Person *p2 = [p1 copy];
NSLog(@"P1 為:%@ P2 為: %@", p1, p2);
實現協議方法,輸出 P1 P2 對象地址
-(id)copyWithZone:(NSZone *)zone
{
Person *per = [[Person allocWithZone:zone]init];
return per;
}
深淺拷貝對比圖
源對象類型 | 拷貝方法 | 副本對象類型 | 是否產生新對象 | 拷貝類型 |
---|---|---|---|---|
NS* | copy | NS* | NO | 淺拷貝(指針拷貝 |
NS* | MutableCopy | NSMutable* | YES | 深拷貝(內容拷貝) |
NSMutable* | copy | NS* | YES | 深拷貝(內容拷貝) |
NSMutable* | MutableCopy | NSMutable* | YES | 深拷貝(內容拷貝) |
自定義對象:
自定義對象不存在可變和不可變對象,所以均為深拷貝
深拷貝 淺拷貝
通過打印上述 P1 P2 對象,我們發現,內存地址是不一樣的(深拷貝),這里我們引入深拷貝和淺拷貝的概念。
淺拷貝
"淺拷貝可以簡單的理解為把對象的引用復制,或者說對象的指針.指針賦值
,使兩個指針指向相同的一塊內存空間,操作不安全。“
深拷貝
”深拷貝拷貝當前指針指向的對象,系統會隨機給拷貝的對象重新分配一塊內存,深拷貝以后,兩份對象的內存地址不一樣,指針指向也不一樣
。深拷貝會把當前容器中的對象重新拷貝一份放到另一個容器中,拷貝后的指針指向新的容器。不僅拷貝了對象還把指針頁拷貝了”
只有從不可變對象copy到不可變對象的時候才是淺拷貝,其他的都是深拷貝
總結
1、對不可變的對象進行mutableCopy操作,是進行了一次深拷貝,返回的對象是可變的對象。
2、對不可變的對象進行copy操作,進行了一次淺拷貝,返回一個不可變的對象。
3、對可變得對象進行copy,進行了深拷貝,產生了不可變的對象副本。
4、對可變的對象進行了一次mutableCopy,是進行了一次深拷貝, 返回的對象是一個可變的對象。
5、想要讓自定義的對象支持copy和mutableCopy那么就要對應實現NSCopying協議,和NSMutableCopying協議。
非容器類對象的copy和mutableCopy,以 NSString 為例
NSString *String = @"字符串";
self.aNewStr = [String copy];
self.twoNewStr =[String mutableCopy];
NSLog(@"1*******%p", String);
NSLog(@"2*******%p", _aNewStr);
NSLog(@"2*******%p", _twoNewStr);
結果輸出
2016-06-16 22:54:01.397 oc深淺拷貝[10541:843917] 1*******0x1099bd078
2016-06-16 22:54:01.397 oc深淺拷貝[10541:843917] 2*******0x1099bd078
2016-06-16 22:54:01.398 oc深淺拷貝[10541:843917] 2*******0x7fd959f1d820
通過打印結果我們可以看出:
- 通過 Copy 的字符串,不會產生新對象和String的內存地址一樣,這里 copy 為淺拷貝(指針拷貝)
- 通過 mutableCopy 的字符串,產生的 twoNewStr 和原始的字符串 String 地址不一樣,這里產生了新的對象,為深拷貝(內容拷貝)
@property 聲明中用 copy 修飾 NSString、NSArray、NSDictionary
因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性.
copy 此特質所表達的所屬關系與 strong 類似。然而設置方法并不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那么設置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份。
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];
查看內存,會發現 string、stringCopy 內存地址都不一樣,說明此時都是做內容拷貝、深拷貝。即使你進行如下操作:
[string appendString:@"origion!"]
stringCopy 的值也不會因此改變,但是如果不使用 copy,stringCopy 的值就會被改變。 集合類對象以此類推。 所以,
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
retain 和 copy 的區別
1、retain是對當前對象增加了一個指針指向,使對象的引用計數器加1, 是進行了一次安全的淺拷貝操作。
2、copy是對當前對象進行了一次拷貝,重新拷貝了當前對象,當使用的時候減少了對當前對象的依賴
當產生子類時,copyWithZone: 方法需要注意的點
如果你的類產生了子類
,那么copyWithZone:方法也將被繼承。
Student *stu = [[Student allocWithZone: zone] init];
該方法應該改為:
Student *stu = [[[self class] allocWithZone: zone]init];
如果編寫一個類的copyWithZone:方法,那么子類的方法應該先調用父類的copy方法以復制繼承來的copy實例變量.
//LIMING 繼承了 Student 在 copy 方法中,要先調用父類的 copy 方法, super
LIMING *copy = [super copyWithZone:zone];