雖然寫了不少年的iOS代碼,可是不少東西沒有深刻理解,或者當時理解了,後來不用又慢慢又忘了。因此抽空整理一份資料,以備本身之後查找。也但願看到的小夥伴批評指正。
這篇文章主要寫@property後面的修飾符。html
assign:既能夠修飾對象,也能夠修飾基本類型。unsafe_unretained與assign同義。assign修飾對象會產生野指針的問題,修飾的對象釋放後,指針不會自動置成nil,此時再向對象發消息程序會崩潰。安全
weak:只能修飾對象,若是修飾基本數據類型會報錯:Property with 'weak' attribute must be of object type. weak修飾的對象釋放後(引用計數變爲0),指針會被自動置爲nil,以後再向該對象發消息也不會崩潰(向nil發送任何消息都不會崩潰)。weak適用於delegate和block等引用類型,不會致使野指針問題,也不會循環引用,很是安全。app
總結:
assign、weak和unsafe_unretained,修飾的屬性,再調用set方法時是不會增長引用計數的。能夠理解爲set方法只是簡單的賦值。也就是棧上的變量的賦值。驗證代碼以下:atom
@property(nonatomic,assign) NSObject *property; //在代碼實現中 NSObject *object = [[NSObject alloc] init]; NSLog(@"\n 引用計數 = %lu \n ", (unsigned long)[object retainCount]); self.property = object; NSLog(@"\n 引用計數 = %lu \n ", (unsigned long)[object retainCount]);
輸出結果
2019-08-05 02:02:08.475192+0800 test4[89384:3597709]
引用計數 = 1
2019-08-05 02:02:08.475320+0800 test4[89384:3597709]
引用計數 = 1 線程
因此assign、weak和unsafe_unretained修飾的對象,默認生成的set方法,應該以下方法一所示:
-(void)setProperty:(NSObject *)property{指針
_property = property; //weak可能還有其餘一些操做
}code
而retain和strong修飾的對象,大概生成的set方法以下方法二所示:
-(void)setProperty:(NSObject *)property{htm
[property retain]; [_property release]; _property = property;
}對象
注意:
若是本身實現了set方法,則這些關於存取方法的修飾符就再也不起做用了。好比,你雖然用assign修飾了對象,可是本身實現了set方法二,則assign會被忽略。ci
在蘋果的文檔裏有這樣一句話:
// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
因此在ARC下他們是等價的。
一般在ARC環境裏,不會用assign修飾對象,由於有了weak,再用assign就是自找麻煩。總之在ARC環境裏用strong代替retain強引用對象,用weak弱引用對象,用assign修飾基本數據類型。
atomic和nonatomic的區別在於:系統自動生成的 getter/setter 方法不同。(若是你本身寫 getter/setter,那 atomic/nonatomic/retain/assign/copy 這些關鍵字只起提示做用,寫不寫都同樣)
對於atomic的屬性,系統生成的 getter/setter 會保證 get、set 操做的完整性,不受其餘線程影響。好比,線程 A 的 getter 方法運行到一半,線程 B 調用了 setter:那麼線程 A 的 getter 仍是能獲得一個無缺無損的對象。
而nonatomic就沒有這個保證了。因此,nonatomic的速度要比atomic快。
nonatomic系統合成的方法大概以下:
@property(nonatomic, retain) UITextField *userName; //系統生成的代碼以下: - (UITextField *) userName { return userName; } - (void) setUserName:(UITextField *)userName_ { [userName_ retain]; [userName release]; userName = userName_; }
atomic系統合成的方法大概以下:
//@property(retain) UITextField *userName; //系統生成的代碼以下: - (UITextField *) userName { UITextField *retval = nil; @synchronized(self) { retval = [[userName retain] autorelease]; } return retval; } - (void) setUserName:(UITextField *)userName_ { @synchronized(self) { [userName release]; userName = [userName_ retain]; } }
假設有一個 atomic 的屬性 "userName",若是線程 A 調[self setUserName:@"A"],線程 B 調[self setUserName:@"B"],線程 C 調[self userName],那麼全部這些不一樣線程上的操做都將依次順序執行——也就是說,若是一個線程正在執行 getter/setter,其餘線程就得等待。所以,屬性 setUserName 是讀/寫安全的。
可是,若是有另外一個線程 D 同時在調[userName release],那可能就會crash,由於 release 不受 getter/setter 操做的限制。也就是說,這個屬性只能說是讀/寫安全的,但並非線程安全的,由於別的線程還能進行讀寫以外的其餘操做。線程安全須要開發者本身來保證。
若是 userName 屬性是 nonatomic 的,那麼上面例子裏的全部線程 A、B、C、D 均可以同時執行,可能致使沒法預料的結果。若是是 atomic 的,那麼 A、B、C 會串行,而 D 仍是並行的。
readwrite:默認的屬性修飾符。會合成get和set方法。
readonly:只會生成get方法。因此若是obj用readonly修飾,則self.obj = xxx;編譯時會報錯提示:「Assignment to readonly property」。
copy:只能修飾對象類型,不能修飾基礎數據類型(用了會報編譯時錯誤)。用copy修飾的對象,必須實現
NSCopying協議也就是實現方法-(id)copyWithZone:(nullable NSZone *)zone。合成的set方法中會調用這個方法。這裏也能看出copy,和retain不同的地方。
至於copy是深拷貝仍是淺拷貝徹底是看copyWithZone的實現方式。也就是說copy和深拷貝和淺拷貝沒有關係!
另外對象的mutableCopy方法,也是要對象知足NSMutableCopying協議,實現- (id)mutableCopyWithZone:(nullable NSZone *)zone方法。若是不實現次方法會運行時報錯!
總結
修飾符其實就是告訴編譯器怎麼來合成存取方法的。有些修飾符須要本身另外實現一些方法,好比copy,這裏就能定製一些本身的東西,好比實現真正的多層深拷貝等等。
另外,還有一些新的修飾符nullable、nonnull、null_resettable、null_unspecified、getter=method、setter=method等,咱們下一篇文章裏再總結.