問題
視頻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,減少小型設備中性能消耗。