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

問題

視頻What’s New in LLVM
中,從12:05的時間開始有個關於NSMutableArray可變數組屬性的使用問題。


運行後報錯圖如下:

分析

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章