問題
視訊What's New in LLVM 中,從12:05的時間開始有個關於NSMutableArray可變陣列屬性的使用問題。
執行後報錯圖如下:分析
self.photos的實際型別是 __NSMutable0,也就NSArray型別。沒有addObject的方法。
進一步探討
-
OC是門動態型語言,在編譯階段不會做型別檢測。OC的記憶體管理是引用計數,在ARC環境下,屬性@property的記憶體管理語義關鍵字有copy,weak,strong,asssin。在編譯階段,預設情況下編譯器會生成一個成員變數、一個setter方法、一個getter方法。而在setter方法中,會根據記憶體管理語義做相應的引用計數相關的操作。當使用copy修飾屬性時,在setter中實際操作是拷貝了一份不可變的型別物件。這樣的話,即使是其是可變型別,在被賦值後,我們得到的是卻是不可變型別的物件。
-
OC具有多型性,父類可以指向子類。物件最終型別會在執行期根據例項化物件確認。在執行時階段其isa指向的是[NSArray Class]。那麼當向self.photos傳送一個addObject訊息時,self.photos物件是接收不到這個訊息的。因為addObject是NSArray的子類NSMutbleArray的方法。
-
屬性語義多種:
- 原子性(Atomicity):原子性(atomic)、非原子性(nonatomic)
- Setter語義(Setter Semantics):strong,weak,copy,asssin
- 讀寫屬性(Writability): readwrite/readonly 原子性是具有執行緒安全的,會在屬性的setter方法內部加個一個自旋鎖、而非原子性是不會在setter方法中加鎖的,是非執行緒安全的。在小型裝置的上,記憶體空間是有限的。給屬性加自旋鎖是非常消耗資源的。並且不一定說使用了原子性就能保證該屬性執行緒安全。這個僅僅是在setter方法中是安全的,這也是atomic該做的事。如果繞開setter方法使用其他的方式給屬性賦值,依然是不安全的,比如使用KVC。
- ARC下,屬性的預設語義是:
- 基本資料:atomic、assgin、readwrite
- 普通的OC物件:atomic、strong、readwrite
在此情況下,實際編譯器新增setter方法如下:
// ARC
- (void)setPhotos:(NSMutableArray<UIImage *> *)photos{
// 1. 開始加鎖,非自然語言,這裡不寫程式碼了
_photos = [photos copy];
// 2. 加鎖結束
}
複製程式碼
那麼得到的是個self.photos實際是NSArray類。
從上就發現了2個問題:屬性就是使用了關鍵字atomic、copy修飾。那麼這裡會加鎖並且得到NSArray類的self.photos。
相關概念:
- 自旋鎖:當上一個執行緒的任務沒有執行完畢的時候(被鎖住),那麼下一個執行緒會一直等待(不會睡眠),當上一個執行緒的任務執行完畢,下一個執行緒會立即執行。
- 自旋鎖應用場景: 比較適合做一些不耗時的操作
解決
1、修改copy語義在setter中預設內容:
方式一: 手動重寫setter方法,使用賦值前mutableCopy。如下,這樣獲取到的就是NSMutableArray型別的物件。
- (void)setPhotos:(NSMutableArray<UIImage *> *)photos{
_photos = [photos mutableCopy];
}
複製程式碼
方式二:
使用關鍵字strong
修飾屬性。我們得到的依然是可變型別。
- (void)setPhotos:(NSMutableArray<UIImage *> *)photos{
_photos = photos;
}
複製程式碼
2、原子性修改: 使用:nonatomic,減少小型裝置中效能消耗。