健壯的例項變數 (Non Fragile ivars)和脆弱的例項變數(Fragile ivars)

GabrielPanda發表於2019-01-21

1、Non Fragile ivars

objc_class 結構體

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
 
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
 
} OBJC2_UNAVAILABLE;

複製程式碼

objc_ivar結構體

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}                                                            OBJC2_UNAVAILABLE;

複製程式碼

物件地址 + ivar偏移位元組
在編譯類時,編譯器生成了一個 ivar 佈局,顯示了在類中從哪可以訪問ivars 對 ivar 的訪問就可以通過 物件地址 + ivar偏移位元組的方法

父類增加例項變數

蘋果更新了NSObject類,釋出新版本的系統,當增加了父類的ivar,這個時候佈局就出錯了,就不得不重新編譯子類來恢復相容性。 (那如果是線上上執行的app,升級系統後就沒辦法執行了)

相容父類的變化

使用 Non Fragile ivars時,Runtime會進行檢測來調整類中新增的 ivar 的偏移量。 這樣就可以通過物件地址 + 基類大小 + ivar偏移位元組 的方法來計算出ivar相應的地址,並訪問到相應的ivar。(即使升級iOS系統,之前的app也能正常執行)

2、Non Fragile ivars的意義

Objective-C的庫從此具有了**“二進位制相容性”**。 比如在專案裡用了第三方提供的靜態庫SDK,包含一些.h和一個.a檔案。當iOS SDK的版本從11升到了12,都不需要更新這個SDK。雖然iOS SDK版本升級時,蘋果在等基類中加入了更多的成員變數,但是以前釋出的靜態庫SDK不需要重新編譯還能正常使用。

iOS從一開始就是用的modern runtime。 以前的Mac開發者每次MacOS釋出新版本,都要重新編譯自己的程式,跟著釋出新版本。

3、引申-為什麼OC類不能動態新增成員變數

既然允許用Category給類增加方法和屬性,那為什麼不允許增加成員變數? 在Objective-C提供的runtime函式中,確實有一個class_addIvar()函式用於給類新增成員變數,但是文件中特別說明:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

這個函式只能在“構建一個類的過程中”呼叫。 一旦完成類定義,就不能再新增成員變數了。 經過編譯的類在程式啟動後就被runtime載入,沒有機會呼叫addIvar。 程式在執行時動態構建的類需要在呼叫objc_registerClassPair之後才可以被使用,同樣沒有機會再新增成員變數。

為基類動態增加成員變數會導致所有已建立出的子類例項都無法使用。

那為什麼runtime允許動態新增方法和屬性,而不會引發問題呢?

因為方法和屬性並不“屬於”類例項,而成員變數“屬於”類例項。我們所說的“類例項”概念,指的是一塊記憶體區域,包含了isa指標和所有的成員變數。 所以假如允許動態修改類成員變數佈局,已經建立出的類例項就不符合類定義了,變成了無效物件。 但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響類例項的記憶體佈局,已經建立出的類例項仍然可正常使用。

相關文章