OC對象的本質(下)—— 詳解isa&superclass指針

OC對象的本質(上):OC對象的底層實現原理git

OC對象的本質(中):OC對象的種類
github

isa指針

先總結一下咱們在對象的分類一文裏面分析過的問題,OC對象氛圍三類面試

instance對象,內部包含markdown

  • 成員變量
  • 特殊的成員變量isa指針

class對象,用來描述instance對象,內部包含app

  • isa指針
  • superclass指針
  • 屬性信息
  • 對象方法信息(-方法)
  • 協議信息
  • instance對象的成員變量的描述信息

meta-class對象,用來存放類方法,內部包含ide

  • isa指針
  • superclass指針
  • 類方法信息(+方法)

oc對象的分類以及內部結構

對一個類來講,它的instanceclassmete-class對象之間,必定是有某種聯繫的。假設這種聯繫不存在,咱們看看會碰到什麼問題。好比我調用一個instance對象的方法oop

[InstanceObj InstanceObjMethod];
複製代碼

它的底層是post

objc_msgSend(instanceObj, @sel_registerName("instanceObjMethod"));
複製代碼

也就是給instanceObj對象發消息。ui

instance對象不跟外界關聯的狀況下,它內部只有一些成員變量信息,是不可能完成方法調用的,由於對象方法是存放在class對象裏面的,對class對象調用+方法的時候也是同樣,必須有辦法跟meta-class對象關聯起來,才能完成對+方法的調用。因此isa指針,就只它們之間的關聯。能夠經過下圖來理解isa的做用。spa

isa指針的做用

大體能夠概括爲

  • instance對象isa指針指向class對象。當調用對象方法(-方法)時,經過instanceisa找到class,而後在class的方法列表裏面找到對應的實現進行調用。
  • class對象的isa指針指向meta-class對象。當調用類方法(+)方法時,經過classisa找到meta-class,最後在meta-class的方法列表找到對應的實現進行調用。

那麼經過isa的橋接做用,我夢應該能更近一步地理解OC消息發送以及方法調用的過程了。

superclass指針

顯而易見,從字面意思,咱們就能知道,superclass就是父類的意思。 假定咱們有如下幾個類

@interface Person : NSObject
@end

@interface Student : Person
@end
複製代碼

咱們知道superclass指針存在於class對象meta-class對象裏面。咱們根據接下來的圖示來闡述一下:

class的superclass指針

一個類的class對象裏面的superclass指針指向該類的父類的class對象 Studentinstance對象要調用Person的對象方法時,會先經過isa找到Studentclass對象,而後經過這個class對象superclass找到Person(Student的父類)class對象,最後找到相應的對象方法(-方法)的實現進行調用

meta-class的superclass指針

一個類的meta-class裏面的superclass指針指向該類的父類的meta-class對象 Studentclass對象要調用Person的類方法時,會先經過isa找到Studentmeta-class對象,而後經過這個meta-class對象superclass找到Person(Student的父類)meta-class對象,最後找到相應的類方法(+方法)的實現進行調用

isa、superclass總結

isa、superclass指針做用圖例 上圖來自蘋果官方,完整描述了isa、superclass指針的做用,爲了更加便於理解,咱們在後面的圖例中用Student代替subclass,Person代替superclass,NSObject代替rootclass。

  • instanceisa指向class
  • classisa指向meta-class
  • meta-classisa指向基類的meta-class
  • classsuperclass指向父類的class若是沒有父類,superclass指針爲nil
  • meta-classsuperclass指向父類的meta-class,基類的meta-classsuperclass指向基類的class

instance調用對象方法的軌跡 咱們以[student abc];爲例,studentStudent類的實例對象,調用軌跡以下圖

對於student來講,並不知道abc方法在哪裏,惟一知道的就是能夠去它的class對象裏面找,

  • 因而先經過isa指針進入Student類的class對象,若是在其中找到了abc就直接進行調用,調用過程結束,
  • 沒找到的話,就經過class對象superclass指針進入Student類的父類,也就是Person類的class對象,重複上一步的查找邏輯
  • 以此類推,一層一層往上尋找,若是最終到了基類,也就是NSObject類的class對象裏面,還沒找到的話,因爲它的superclassnil,最終就會碰到一個經典的報錯[ERROR: unrecognized selector sent to instance],調用軌跡結束

class調用類方法的軌跡 咱們以[Student abc];爲例調用軌跡圖以下

對與Student類來講,abc在哪也是不知道的,咱們知道類方法被規定放在meta-class對象裏面,因此

  • 首先,經過Studentclass對象isa指針找到其meta-class對象,而後在方法列表裏面尋找是否有abc,有的話就調用,調用邏輯結束。
  • 沒有的話,就經過meta-class對象superclass指針找到Student的父類Personmeta-class對象,而後查找abc方法,找到就調用,結束調用軌跡
  • 沒有的話,就經過Personmeta-class對象superclass指針,重複上一步的流程
  • 一次類推,經過meta-class對象superclass指針,一層層往上查找
  • 若是到了基類(NSObject)的meta-class還沒可以找到abc,此時比較特殊,接下來的superclass指針會找到NSObject的class對象,你可能會奇怪,咱們調用一個類方法,怎麼跑到class對象裏面來了,先保留你的疑問,只需記住,蘋果確實是這麼設計的,此時會繼續在NSObject的class對象裏面,尋找abc,若是真的找到了abc,就會調用
  • 若是尚未找到,因爲此時的superclassnil,最終系統將給出報錯

面試題 isa指針指向哪裏?

根據咱們上面的梳理和總結,咱們能夠得出結論

isa(of instance) --> isa(of class) --> isa(of meta-class)

下面咱們經過代碼來驗證一下

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface CLPerson : NSObject <NSCopying>

@end

@implementation CLPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLPerson *person = [[CLPerson alloc] init];
        Class personClass = [CLPerson class];
        Class personMetaClass = object_getClass(personClass);
        NSLog(@"%p %p %p", person, personClass, personMetaClass);
    }
    return 0;
}
複製代碼

咱們在代碼中加入斷點,經過控制檯查看一下personisa信息。可是貌似系統只給出了有限信息 還有個辦法,能夠右擊紅框中的isa,下拉菜單第一個有個打印功能Print Description of "xxx",能夠獲得更爲詳細的輸出 看起來結果仍然被系統包裹了一層 若是你習慣直接在代碼上快捷操做,也能夠這麼作試試 但我仍是喜歡用LLDB來查看,便於比較,和複製數據。 經過p/x命令來打印指針,/後面是打印參數,x參數表示用16進制數輸出。由於咱們知道person這個instance的結構體的包含一個isa成員變量,person自己就是指針,因此能夠經過person->isa訪問isa的值。 代碼裏面,personClassPerson類class對象,輸出結果顯示, person的isa = 0x001d8001000014d1 personClass = 0x00000001000014d0 它倆。。。並不相等!!! 這是什麼狀況?不是說好了instance對象isa指向class對象嘛?

其實在64位機器出現以前,instance對象isa確實是直接指向class對象的, 也就是
person->isa == personClass, 從64bit開始,isa須要進行一次爲運算,才能計算出真實的class對象地址,系統給咱們提供了一個ISA_MASK,這個能夠在objc4源碼裏面找到。我先直接貼出來

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif
複製代碼

你們請看清這裏是分了arm64和x86_64的,分別對應的是移動設備開發和mac開發。個人代碼是一個mac命令行工程,因此咱們用x86的這個值來試一下 能夠看到,結果就顯而易見了。經過和ISA_MASK進行一次&運算,咱們獲得了personClass的地址。一樣,咱們來試一下personClass的isa指針。 結果我試圖經過personClass->isa先打印出其isa指針的時候,獲得了錯誤提示,告訴咱們說personClass的類型Class不是一個結構體,看不太明白,那就先查看一下Class的定義,typedef struct objc_class *Class;,而後在往下看一下objc_class的細節

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
複製代碼

雖然這個結構體裏面有isa指針,可是尾部的OBJC2_UNAVAILABLE;提示咱們,這已是過期的API了。 不過咱們在第一篇文章中,已經得出結論,知道class對象裏面第一個成員變量確實是一個isa指針,咱們能夠經過一個小技巧來處理這個問題

struct cl_objc_class {
    Class isa;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        CLPerson *person = [[CLPerson alloc] init];
        Class personClass = [CLPerson class];
        struct cl_objc_class *personClass2 = (__bridge struct cl_objc_class *)(personClass);
        Class personMetaClass = object_getClass(personClass);
        NSLog(@"%p %p %p", person, personClass, personMetaClass);
    }
    return 0;
}
複製代碼

咱們自定義一個struct,包含一個isa指針,而後再借助這個結構體類型來讀取personClass裏面的內容,如上代碼,咱們用personClass2在來嘗試一次 ok,結果顯示,class對象isa指針通過ISA_MASK轉換以後,獲得了正確的mete-class對象的地址。到此,上面的面試題相信你們已經能夠完整回答了。在用一個圖來總結一下就是

你會許還會問,那麼superclass指針呢,是否是也須要一個什麼mask轉換?答案是不須要的,能夠用上面相同的方法進行驗證,這裏不做贅述。總之isa指針稍微特殊一點點,特別記住一下關於ISA_MASK的細節就行。

深度窺探class/meta-class的內部結構----struct objc_class

在OC對象的本質(一)中,咱們得知了一個事實,在class對象中,存放了一個類的方法列表、屬性信息、協議信息、成員變量信息;在meta-class對象中,存放了類的類信息。可是尚未對其仔細驗證過。下面咱們就來研究一下這個問題。 由於classmeta-class的類型都是struct objc_class*,因此咱們問題的答案,就都在這個objc_class裏面。上面的段落咱們已經看了它的結構了,很惋惜是一個已經廢棄的API,因此咱們必須去最新的源碼裏面,去看一下它的實現。在objc4源碼裏面,咱們找到以下objc_class的實現

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
//下面是一大堆的方法
...
...
...
}
複製代碼

這裏的objc_class是一個C++的結構體,若是對C++不太熟的話,先不用過度糾結,能夠借用OC的類來理解就好了,它們的類似度很高,能夠有成員變臉,也能夠有方法,區別主要是一些成員變量的默認做用域不同。能夠看到objc_class繼承自objc_object,咱們能夠在源碼objc-private.h裏看一下objc_object的實現

struct objc_object {
private:
    isa_t isa;
//剩下的都是方法
...
...
...
}
複製代碼

看得出來,其實就是一個isa指針。因而和objc_class的內容融合一下,咱們能夠理解成下面的這個結構

struct objc_class {
    isa_t isa;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
//下面是一大堆的方法
...
...
...
}
複製代碼

很明顯,objc_class的內部,頭兩個成員分別是isa和superclass,跑不了。可是下面的好像不是咱們期待的內容,沒看到方法列表、屬性協議信息啥的呀。可是我門能夠看到這裏的第一個方法,返回一個class_rw_t *,看字面意思,class表明類,rw一般表明讀寫(readwrite),t一般指的是 表信息(table),也就是類的可讀寫信息。那麼咱們有理由懷疑這裏面確定有寶貝。進去看一看 果真,原來方法、屬性、協議信息都放在了這裏。同時,咱們還發現了一個class_ro_t *ro,字面就是隻讀表,類對象裏面有什麼信息是隻讀的呢?沒錯,成員變量信息,因而咱們在進去驗證一下 看上去推斷是對的,確實找到成員變量信息。注意一下,這裏的ivars是成員變量的描述信息,如名稱,類型等,只須要一份的,因此存在class對象裏面,成員變量的具體值是存在具體的instance對象裏面的,不要理解混了。

對於meta-class來講,結構上和class是同樣的,只不過有些內容可能用不到,例如屬性,協議列表。meta-class的類方法信息其實就放在咱們剛纔看到的那個方法列表裏面,沒錯。class對象的對象方法信息也正是放在這個方法列表裏的

還有一點就是,怎麼說呢,仍是看圖明白

途中咱們看出來,objc_class 有個成員變量bits,正是經過 bits & FAST_DATA_MASK,將objc_class 和它的可讀寫表關聯起來了。下面我引用大神的一張ppt總結一下struct objc_class 的結構

面試題解答

  • 對象的isa指針指向哪裏?
  1. instance對象的isa指針指向class對象
  2. class對象的isa指針指向meta-class對象
  3. meta-class對象的isa指針指向基類(也就是NSObject)的meta-class對象
  • OC的類信息存放在哪裏?
  1. 對象方法,屬性信息,成員變量信息,協議信息,存放在class對象中
  2. 類方法,存放在meta-class對象中
  3. 成員變量的具體值,存放在instance對象中

OC對象的本質(上):OC對象的底層實現原理

OC對象的本質(中):OC對象的種類

特別備註

本系列文章總結自MJ老師在騰訊課堂開設的OC底層原理課程,相關圖片素材均取自課程中的課件。

相關文章
相關標籤/搜索