深入理解 Objective-C 屬性記憶體管理語義(assign、weak、copy、strong)

語言: CN / TW / HK

深入理解 Objective-C 屬性記憶體管理語義

一文徹底搞懂 assign、weak、copy、strong 這些記憶體管理語義的用法與原理

舉個例子:

``` // // Dog.h // oc-demo // // Created by 姚明振 on 2022/8/17. // ​

import

​ NS_ASSUME_NONNULL_BEGIN ​ @interface Dog : NSObject ​ @property(nonatomic, assign, nullable) NSObject assignProperty; @property(nonatomic, weak, nullable) NSObject weakProperty; @property(nonatomic, copy, nullable) NSString propertyCopy; @property(nonatomic, strong, nullable) NSObject strongProperty; @end ​ NS_ASSUME_NONNULL_END ​ ```

assign: image.png 想要它不 crash 其實是有辦法的,我們在它所指向物件被銷燬時手動幫它為屬性賦 nil:image.png當然這沒有什麼實際意義,不能實現我們預期的目的。

原因是 alloc、init 方法返回的是一個 autorelease 的 NSObject 例項,當走完 init 後如果沒有人對他進行 release 操作,則立馬會被銷燬。

image.png讓臨時變數持有該 NSObject 例項,即在作用域內被強引用著,所以 assign 修飾的屬性在這種情況下也能正常訪問指向的物件。例子只為讓大家易於理解,千萬不要在專案裡這麼用。

總結:

  • 只進行簡單的賦值操作(把一個指標賦值給該屬性,如果是物件則是物件地址,如果是基本資料型別則是該值的地址)
  • 只適用於基本資料型別(非 Objective-C 物件「語法上是支援修飾 Objective-C 物件的」,如:int,float,NSInterge,CGFloat,及結構體型別)
  • 不影響該屬性所指向物件的生命週期(不改變retaincount)
  • 在所指向物件銷燬時不會自動為該屬性賦 nil 值

weak:

參考 assign,做個對比

image.png 我們發現跟 assign 不同的是,這裡沒有發生 crash,原因是 weak 修飾的屬性在所指向物件銷燬時自動為該屬性賦 nil 處理。image.png使用臨時變數強引用 NSObject,此時能正常訪問該屬性指向的物件。

總結:

  • 只適用於 Objective-C 物件(語法上就不支援修飾基本資料型別)
  • 不影響該屬性所指向物件的生命週期(不改變retaincount)
  • 在所指向物件銷燬時會自動為該屬性賦 nil 值(防止野指標訪問導致 crash)

strong:

image.png可以看到屬性值正常輸出字串 "123",說明對字串進行了強引用。image.png不過當我們賦值 NSMutableString 例項時,發現這違背了該屬性的設計意圖。明明是不可變的字串,剛賦值為123,講道理在不走 setter 方法時它的值是不應該允許改變的,可這種事真的發生了,它被修改為 123456 了。於是引入了 copy 來解決這個問題(下面介紹)。

總結:

  • 只適用於 Objective-C 物件(語法上就不支援修飾基本資料型別)

  • 會強引用指向的物件(retaincount + 1)

  • 會釋放之前指向的物件(retaincount - 1)

  • 當修飾 block 型別屬性時(ARC 下表現與 copy 一致)

    • 當指向的物件為 StackBlock 時會把block物件複製到堆區成為 MallocBlock
    • 當指向的物件為 MallocBlock 時會增加引用計數(retaincount + 1)
    • 當指向的物件為 GlobalBlock 時什麼都不會發生

copy:

當用 copy 修飾一個 NSObject 型別的屬性,發現執行時發生了 crash,並丟擲 NSObject 未實現 copyWithZone: 方法。這是因為 copy 修飾的屬性會在 setter 方法內對老值進行 release,對新值進行 copy 操作,也就是它真正指向的是傳入物件 copy 後的返回值(可能是深 copy 也可能是淺 copy )。這一操作避免了給不可變型別(如:NSString)屬性賦值對應可變子類(如:NSMutableString)導致的邏輯異常。image.png把屬性型別改為 NSString(它實現了copyWithZone: 方法)。

image.png這裡列印了字串 “123”,說明了 copy 修飾的屬性會對它指向的值進行強引用。

另一方面當為該屬性賦值後,繼續修改原值時該屬性的值不受影響,因為此時已經把 NSMutableString copy 為了 NSString。

總結:

  • 只適用於 Objective-C 物件(語法上就不支援修飾基本資料型別)

  • 會強引用指向的物件(retaincount + 1)

  • 會釋放之前指向的物件(retaincount - 1)

  • 當修飾 NSString,NSArray 等存在可變型別的子類時(這是 copy 與 strong 的最大區別)

    • 使用 copy 修飾,把可變型別深 copy 為不可變型別,避免不安全修改
    • 不安全的原因是:可變型別的物件可以在不改變物件地址的情況下修改值,因為你的屬性是 NSString 型別,此時值被意外修改是不符合預期的。
  • 當修飾 block 型別屬性時

    • 當指向的物件為 StackBlock 時會把block物件複製到堆區成為 MallocBlock
    • 當指向的物件為 MallocBlock 時會增加引用計數(retaincount + 1)
    • 當指向的物件為 GlobalBlock 時什麼都不會發生

總結

今天開始複習,決定把自己的理解以文章的形式輸出,一方面能加深理解做個筆記,當然也希望能給大家帶來點幫助。筆者水平有限,難免有理解偏差與遺漏,希望大家批評指正。