整理的一些有關copy關鍵字的解釋、用法及相關注意事項。
1、怎麼用 copy關鍵字?
用途:
1)NSString、NSArray、NSDictionary等等經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
2)block也經常使用copy關鍵字,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks:
block使用copy是從MRC遺留下來的“傳統”,在MRC中,方法內部的block是在棧區的,使用copy可以把它放到堆區.在ARC中寫不寫都行:對於block使用copy還是strong效果是一樣的,但寫上copy也無傷大雅,還能時刻提醒我們:編譯器自動對block進行了copy操作。
下面做下解釋: copy此特質所表達的所屬關係與strong類似。然而設置方法並不保留新值,而是將其“拷貝” (copy)。 當屬性類型爲NSString時,經常用此特質來保護其封裝性,因爲傳遞給設置方法的新值有可能指向一個NSMutableString類的實例。這個類是NSString的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那麼設置完屬性之後,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份。
用@property聲明 NSString、NSArray、NSDictionary經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,爲確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
該問題在下文中也有論述:用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?
2、如何讓自己的類用 copy修飾符?如何重寫帶 copy關鍵字的 setter?
若想令自己所寫的對象具有拷貝功能,則需實現NSCopying協議。如果自定義的對象分爲可變版本與不可變版本,那麼就要同時實現NSCopyiog與NSMutableCopying協議。
具體步驟:
1)需聲明該類遵從NSCopying協議
2)實現NSCopying協議。該協議只有一個方法:
1 |
- (id)copyWithZone: (NSZone*) zone |
注意:一提到讓自己的類用 copy修飾符,我們總是想覆寫copy方法,其實真正需要實現的卻是“copyWithZone”方法。
以下列代碼爲例:
然後實現協議中規定的方法:
但在實際的項目中,不可能這麼簡單,遇到更復雜一點,比如類對象中的數據結構可能並未在初始化方法中設置好,需要另行設置。舉個例子,假如CYLUser中含有一個數組,與其他CYLUser對象建立或解除朋友關係的那些方法都需要操作這個數組。那麼在這種情況下,你得把這個包含朋友對象的數組也一併拷貝過來。下面列出了實現此功能所需的全部代碼:
// .m文件
以上做法能滿足基本的需求,但是也有缺陷:如果你所寫的對象需要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法。
【注:深淺拷貝的概念,在下文中有介紹,詳見下文的:用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?】
在例子中,存放朋友對象的set是用“copyWithZooe:”方法來拷貝的,這種淺拷貝方式不會逐個複製set中的元素。若需要深拷貝的話,則可像下面這樣,編寫一個專供深拷貝所用的方法:
1 2 3 4 5 6 7 8 9 |
- (id)deepCopy { CYLUser *copy = [[[self copy] allocWithZone:zone] initWithName:_name age:_age sex:sex]; copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES]; return copy; } |
至於如何重寫帶 copy關鍵字的 setter這個問題,
如果拋開本例來回答的話,如下:
1 2 3 |
- (void)setName:(NSString *)name { _name = [name copy]; } |
如果單單就上文的代碼而言,我們不需要也不能重寫name的 setter:由於是name是隻讀屬性,所以編譯器不會爲其創建對應的“設置方法”,用初始化方法設置好屬性值之後,就不能再改變了。(在本例中,之所以還要聲明屬性的“內存管理語義”--copy,是因爲:如果不寫copy,該類的調用者就不知道初始化方法裏會拷貝這些屬性,他們有可能會在調用初始化方法之前自行拷貝屬性值。這種操作多餘而低效。)。
那如何確保name被copy?在初始化方法(initializer)中做:
1 2 3 4 5 6 7 8 9 10 11 |
- (instancetype)initWithName:(NSString *)name age:(int)age sex:(CYLSex)sex { if(self = [super init]) { _name = [name copy]; _age = age; _sex = sex; _friends = [[NSMutableSet alloc] init]; } return self; } |
3、用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?
1)因爲父類指針可以指向子類對象,使用copy的目的是爲了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
2)如果我們使用是strong,那麼這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那麼會影響該屬性.
copy此特質所表達的所屬關係與strong類似。然而設置方法並不保留新值,而是將其“拷貝” (copy)。當屬性類型爲NSString時,經常用此特質來保護其封裝性,因爲傳遞給設置方法的新值有可能指向一個NSMutableString類的實例。這個類是NSString的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那麼設置完屬性之後,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份。
爲了理解這種做法,首先要知道,對非集合類對象的copy操作:
在非集合類對象中:對immutable對象進行copy操作,是指針複製,mutableCopy操作時內容複製;對mutable對象進行copy和mutableCopy都是內容複製。用代碼簡單表示如下:
- [immutableObject copy] //淺複製
- [immutableObject mutableCopy] //深複製
- [mutableObject copy] //深複製
- [mutableObject mutableCopy] //深複製
比如以下代碼:
1 2 |
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy NSString *stringCopy = [string copy]; |
查看內存,會發現 string、stringCopy內存地址都不一樣,說明此時都是做內容拷貝、深拷貝。即使你進行如下操作:
1 |
[string appendString:@"origion!"] |
stringCopy的值也不會因此改變,但是如果不使用copy,stringCopy的值就會被改變。集合類對象以此類推。所以,
用@property聲明 NSString、NSArray、NSDictionary經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,爲確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
4. 這個寫法會出什麼問題: @property (copy) NSMutableArray *array;
兩個問題:
1、添加,刪除,修改數組內的元素的時候,程序會因爲找不到對應的方法而崩潰.因爲copy就是複製一個不可變NSArray的對象;
2、使用了atomic屬性會嚴重影響性能。
第1條的相關原因在下文中有論述《用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?》以及上文《怎麼用 copy 關鍵字?》也有論述。
第2條原因,如下:
該屬性使用了同步鎖,會在創建時生成一些額外的代碼用於幫助編寫多線程程序,這會帶來性能問題,通過聲明nonatomic可以節省這些雖然很小但是不必要額外開銷。
在默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備nonatomic特質,則不使用同步鎖。請注意,儘管沒有名爲“atomic”的特質(如果某屬性不具備nonatomic特質,那它就是“原子的”(atomic))。
在iOS開發中,你會發現,幾乎所有屬性都聲明爲nonatomic。
一般情況下並不要求屬性必須是“原子的”,因爲這並不能保證“線程安全” ( thread safety),若要實現“線程安全”的操作,還需採用更爲深層的鎖定機制才行。例如,一個線程在連續多次讀取某屬性值的過程中有別的線程在同時改寫該值,那麼即便將屬性聲明爲atomic,也還是會讀到不同的屬性值。
因此,開發iOS程序時一般都會使用nonatomic屬性。但是在開發Mac OS X程序時,使用 atomic屬性通常都不會有性能瓶頸。