知識點:可變陣列的屬性使用copy修飾的後果

雅之道法自然發表於2018-11-15

知識點:可變陣列的屬性使用copy修飾的後果

問題

視訊What's New in LLVM 中,從12:05的時間開始有個關於NSMutableArray可變陣列屬性的使用問題。

知識點:可變陣列的屬性使用copy修飾的後果
執行後報錯圖如下:
知識點:可變陣列的屬性使用copy修飾的後果

分析

self.photos的實際型別是 __NSMutable0,也就NSArray型別。沒有addObject的方法。

進一步探討

  1. OC是門動態型語言,在編譯階段不會做型別檢測。OC的記憶體管理是引用計數,在ARC環境下,屬性@property的記憶體管理語義關鍵字有copy,weak,strong,asssin。在編譯階段,預設情況下編譯器會生成一個成員變數、一個setter方法、一個getter方法。而在setter方法中,會根據記憶體管理語義做相應的引用計數相關的操作。當使用copy修飾屬性時,在setter中實際操作是拷貝了一份不可變的型別物件。這樣的話,即使是其是可變型別,在被賦值後,我們得到的是卻是不可變型別的物件。

  2. OC具有多型性,父類可以指向子類。物件最終型別會在執行期根據例項化物件確認。在執行時階段其isa指向的是[NSArray Class]。那麼當向self.photos傳送一個addObject訊息時,self.photos物件是接收不到這個訊息的。因為addObject是NSArray的子類NSMutbleArray的方法。

  3. 屬性語義多種:

  • 原子性(Atomicity):原子性(atomic)、非原子性(nonatomic)
  • Setter語義(Setter Semantics):strong,weak,copy,asssin
  • 讀寫屬性(Writability): readwrite/readonly 原子性是具有執行緒安全的,會在屬性的setter方法內部加個一個自旋鎖、而非原子性是不會在setter方法中加鎖的,是非執行緒安全的。在小型裝置的上,記憶體空間是有限的。給屬性加自旋鎖是非常消耗資源的。並且不一定說使用了原子性就能保證該屬性執行緒安全。這個僅僅是在setter方法中是安全的,這也是atomic該做的事。如果繞開setter方法使用其他的方式給屬性賦值,依然是不安全的,比如使用KVC。
  1. 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. 自旋鎖:當上一個執行緒的任務沒有執行完畢的時候(被鎖住),那麼下一個執行緒會一直等待(不會睡眠),當上一個執行緒的任務執行完畢,下一個執行緒會立即執行。
  2. 自旋鎖應用場景: 比較適合做一些不耗時的操作

解決

1、修改copy語義在setter中預設內容:

方式一: 手動重寫setter方法,使用賦值前mutableCopy。如下,這樣獲取到的就是NSMutableArray型別的物件。

- (void)setPhotos:(NSMutableArray<UIImage *> *)photos{
    _photos = [photos mutableCopy];
}
複製程式碼

方式二: 使用關鍵字strong修飾屬性。我們得到的依然是可變型別。

- (void)setPhotos:(NSMutableArray<UIImage *> *)photos{
    _photos = photos;
}
複製程式碼

2、原子性修改: 使用:nonatomic,減少小型裝置中效能消耗。

相關文章