深入理解 Objective-C ? Class

0.前言

從事 iOS 開(kāi)發(fā)已有 3 年多時(shí)間,大部分時(shí)間都是在用 Objective-C 開(kāi)發(fā) App(最近也在做 OC 與 Swift 的混編實(shí)踐),雖然對(duì) OC 底層知識(shí)有一定的了解,不過(guò)都是零散的片段,計(jì)劃趁著過(guò)年的時(shí)間將這些片段梳理串聯(lián)起來(lái),于是便有了這個(gè)系列。

本文是第 1 篇,從我們平常用的最多的類(lèi)對(duì)象開(kāi)始,深入探究他們的實(shí)現(xiàn)機(jī)理。

1.概述

我們平常編寫(xiě)的 OC 代碼都會(huì)先編譯成 C/C++代碼,然后再依次翻譯成 匯編代碼機(jī)器碼(01代碼),最后,機(jī)器會(huì)自動(dòng)運(yùn)行該機(jī)器語(yǔ)言程序,并將計(jì)算結(jié)果輸出。為了探究 OC 的本質(zhì),通過(guò) C/C++ 是比較合適的方式,因?yàn)橹蟮膮R編和01代碼看著太費(fèi)勁(主要是自己只了解點(diǎn)皮毛(⊙﹏⊙)b),而 OC 本身又不是開(kāi)源的。

2.從一個(gè) ?? 開(kāi)始

2.1 最簡(jiǎn)單的例子

我們先來(lái)看一個(gè)例子:

// 以下代碼位于 main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 創(chuàng)建一個(gè) NSObject 的實(shí)例對(duì)象
        NSObject *obj = [[NSObject alloc] init];
    }
    return 0;
}

如上所示,在 main() 函數(shù)里邊創(chuàng)建了一個(gè) NSObject 的實(shí)例對(duì)象,然后終端執(zhí)行下邊的指令,將代碼編譯成 C/C++ 代碼 (新代碼在 main.cpp 文件中):

clang -rewrite-objc main.m -o main.cpp //  -o main.cpp 可以忽略

在 main.cpp 中我們發(fā)現(xiàn)了下邊的結(jié)構(gòu)體,從名字推斷,應(yīng)該是 NSObject 的底層實(shí)現(xiàn):

struct NSObject_IMPL { // NSObject_IMPL <=> NSObject implementation 
    Class isa;
};

而我們直接查看 NSobject 的聲明:

@interface NSObject <NSObject> { // 移除了用于消除警告的代碼 
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject_IMPL 對(duì)比后,進(jìn)一步印證了 NSObject_IMPL 是 NSObject 的底層結(jié)構(gòu)的推斷。這里有一個(gè) Class 類(lèi)型的 isa,下面是 Class 的定義:

/// An opaque type that represents an Objective-C class. 表示 OC 中的 class。
typedef struct objc_class *Class;

也就是說(shuō),isa 實(shí)際是一個(gè)指向 struct objc_class 的指針,而且 objc_class 就是 Class 的底層結(jié)構(gòu)。

2.2 稍微復(fù)雜點(diǎn)的例子

現(xiàn)在來(lái)看一種更加復(fù)雜的情況:依次創(chuàng)建 HHStaff 和 HHManager 這 2 個(gè)類(lèi),其中,后者繼承自前者,然后在 main() 函數(shù)中創(chuàng)建一個(gè) HHManager 的實(shí)例。

HHStaff

@interface HHStaff : NSObject {
    NSString *name;
}

- (void)doInstanceStaffWork; // 對(duì)象方法
+ (void)doClassStaffWork;    // 類(lèi)方法

@end

HHManager

@interface HHManager : HHStaff {
    NSInteger officeNum;
}

- (void)doInstanceManagerWork; // 對(duì)象方法
+ (void)doClassManagerWork;    // 類(lèi)方法

main.m 文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 創(chuàng)建實(shí)例對(duì)象
        HHManager *mgr = [[HHManager alloc] init];
    }
    return 0;
}

終端執(zhí)行 clang -rewrite-objc main.m 將其轉(zhuǎn)成 C/C++ 代碼,整理相關(guān)代碼后,我們可以得出下圖的關(guān)系:

稍微復(fù)雜的情況.png

其中,HHManager_IMPL 是 HHManager 的底層結(jié)構(gòu),而 HHStaff_IMPL 是其父類(lèi) HHStaff 的底層結(jié)構(gòu),即子類(lèi)中包含一個(gè)父類(lèi)類(lèi)型的變量,而父類(lèi)結(jié)構(gòu)中又包含一個(gè)父類(lèi)的父類(lèi)(此處是基類(lèi))類(lèi)型變量,而基類(lèi)中包含一個(gè)名為 isa 的指針變量,據(jù)此,可以認(rèn)為子類(lèi) HHManger 經(jīng)編譯后的結(jié)構(gòu)是這樣的:

struct HHManager_IMPL {
    Class isa;
    NSString *name;
    NSInteger officeNum;
};

我們發(fā)現(xiàn),這里包含了一個(gè) isa 指針,而 isa 來(lái)自 NSObject,因?yàn)榇蟛糠诸?lèi)都是直接或間接繼承自 NSObject 的,所以可以認(rèn)為每一個(gè)對(duì)象都包含了一個(gè) isa 指針,至于這個(gè) isa 指針到底是干什么用的,下一小節(jié)就會(huì)講到。

3.OC 的 3 種對(duì)象間的關(guān)系

3.1 OC 中的 3 種對(duì)象

為了搞清楚 isa 指針的作用,有必要先了解一下 OC 的對(duì)象,總共有以下 3 種:

  • 實(shí)例對(duì)象(instance),通過(guò) +alloc 方法創(chuàng)建出來(lái)的,如下邊的 staffA、staffB:
HHStaff *staffA = [[HHStaff alloc] init];
HHStaff *staffB = [[HHStaff alloc] init];

NSLog(@"實(shí)例對(duì)象:%p - %p", staffA, staffB);

實(shí)例對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:isa 指針 和 其他成員變量。

  • 類(lèi)對(duì)象(class),如下邊的 staffClassA、staffClassB:
Class staffClassA = [staffA class]; // <==> Class staffClassA = [[staffA class] class];
Class staffClassB = object_getClass(staffB);
Class staffClassC = [HHStaff class]; // <==> Class staffClassC = [[HHStaff class] class];

NSLog(@"類(lèi)對(duì)象: %p - %p - %p", staffClassA, staffClassB, staffClassC);

類(lèi)對(duì)象中包含的信息如下圖所示,其中,成員變量信息指的是成員變量的描述信息,而非成員變量的值(在實(shí)例對(duì)象里邊)。

  • 元類(lèi)對(duì)象(meta-class),如下邊的 staffMetaClassAstaffMetaClassB:
Class staffMetaClassA = object_getClass(staffClassA);
Class staffMetaClassB = object_getClass(staffClassB);

NSLog(@"元類(lèi)對(duì)象:%p - %p", staffMetaClassA, staffMetaClassB);

元類(lèi)對(duì)象的存儲(chǔ)結(jié)構(gòu)與類(lèi)對(duì)象相似,只不過(guò)只有 isa、superclass 和 類(lèi)方法有值,其它均為空。

運(yùn)行上邊的程序后,控制臺(tái)的輸出如下:

2019-01-28 17:36:33.990939+0800 TTTTT[10186:1017842] 實(shí)例對(duì)象:0x100605920 - 0x100606060
2019-01-28 17:36:33.991128+0800 TTTTT[10186:1017842] 類(lèi)對(duì)象:  0x100001260 - 0x100001260 - 0x100001260
2019-01-28 17:36:33.991180+0800 TTTTT[10186:1017842] 元類(lèi)對(duì)象:0x100001238 - 0x100001238
Program ended with exit code: 0

從上述打印結(jié)果可以看出,一個(gè)類(lèi)的實(shí)例對(duì)象可以有多個(gè),但是類(lèi)對(duì)象和元類(lèi)對(duì)象各自只有一個(gè)。

3.2 isa 和 superclass

通過(guò)上一小節(jié),我們知道類(lèi)里邊的信息并不是存在一個(gè)地方,而是分開(kāi)存放在實(shí)例對(duì)象、類(lèi)對(duì)象和元類(lèi)對(duì)象里邊。而將這些對(duì)象聯(lián)系起來(lái)的紐帶就是本小節(jié)要重點(diǎn)討論的 isa 和 superclass 指針。

isa

isa.png

isa 是用來(lái)聯(lián)系同一個(gè)類(lèi)的實(shí)例對(duì)象、類(lèi)對(duì)象和元類(lèi)對(duì)象的(isa 類(lèi)型是 isa_t,后文會(huì)講到),如上圖所示,通過(guò)實(shí)例對(duì)象里邊的 isa 指針可以找到類(lèi)對(duì)象,根據(jù)類(lèi)對(duì)象里邊的 isa 指針可以找到元類(lèi)對(duì)象。

注意,這里并沒(méi)有說(shuō) isa 指向哪里,而是說(shuō)通過(guò) isa 可以找到哪里,這是因?yàn)閺?64bit 架構(gòu)開(kāi)始,isa 里邊存儲(chǔ)的不再是類(lèi)對(duì)象或者元類(lèi)對(duì)象的地址,而是需要進(jìn)行一次位運(yùn)算 isa.bits & ISA_MASK(依據(jù)見(jiàn)后文 isa_t 的介紹)才能得到相應(yīng)的地址,其中 ISA_MASK 的定義如下:

# if __arm64__      // 64位 真機(jī)
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__   // 64位 模擬器
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif

注意到 ISA_MASK 中有些位是 0,而和 0 與的話,結(jié)果會(huì)被置為 0,所以可以推測(cè),64bit 架構(gòu)下,isa 里邊可能還存儲(chǔ)了其它信息。

superclass

superclass 是用來(lái)在繼承體系中搜尋父類(lèi)的,如下圖所示:

superclass.png
  • 對(duì)于類(lèi)對(duì)象:子類(lèi)(HHManager)的類(lèi)對(duì)象的 superclass 指向父類(lèi)(HHStaff)的類(lèi)對(duì)象,父類(lèi)的類(lèi)對(duì)象的 superclass 指向它的父類(lèi)的類(lèi)對(duì)象;
  • 對(duì)于元類(lèi)對(duì)象:子類(lèi)(HHManager)的元類(lèi)對(duì)象的 superclass 指向父類(lèi)(HHStaff)的元類(lèi)對(duì)象,父類(lèi)的元類(lèi)對(duì)象的 superclass 指向它的父類(lèi)的元類(lèi)對(duì)象;

3.3 應(yīng)用

下面我們來(lái)看看在消息發(fā)送過(guò)程中,這 3 種對(duì)象之間是如何親密協(xié)作的。

先貼一張經(jīng)典的關(guān)系圖,實(shí)際就是將上一節(jié)中的 isa 和 superclass 指針?lè)诺搅艘黄穑?/p>

現(xiàn)在以 2.2 節(jié)中的例子為基礎(chǔ),執(zhí)行下邊的操作,即子類(lèi)執(zhí)行父類(lèi)的對(duì)象方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 創(chuàng)建實(shí)例對(duì)象
        HHManager *mgr = [[HHManager alloc] init];
        // 執(zhí)行父類(lèi)的方法
        [mgr doInstanceStaffWork]; 
        // => objc_msgSend(mgr, @selector(doInstanceStaffWork));
    }
    return 0;
}

由于對(duì)象方法存放在類(lèi)對(duì)象里邊,所以首先根據(jù) mgr 的 isa 指針找到它的類(lèi)對(duì)象,然后在類(lèi)對(duì)象的方法列表里邊查找這個(gè)方法,發(fā)現(xiàn)找不到,接著再根據(jù)類(lèi)對(duì)象的 superclass 指針找到父類(lèi)的類(lèi)對(duì)象,然后在父類(lèi)的類(lèi)對(duì)象里邊查找該方法,如果還找不到,就根據(jù)父類(lèi)的 superclass 指針沿著繼承體系繼續(xù)往上找,直到根類(lèi),如果還是找不到,就會(huì)執(zhí)行消息轉(zhuǎn)發(fā)的流程(詳見(jiàn) Objective-C 的消息轉(zhuǎn)發(fā)機(jī)制)。不過(guò),本例中父類(lèi)的類(lèi)對(duì)象里有這個(gè)方法,就不用再往上找了O(∩_∩)O。

如果是類(lèi)方法,則通過(guò)類(lèi)對(duì)象的 isa 指針找到元類(lèi)對(duì)象,然后就依照類(lèi)似查找對(duì)象方法的方式查找類(lèi)方法,只不過(guò)這次是在元類(lèi)對(duì)象的繼承體系里邊查找。

其實(shí),上邊的邏輯省略了一個(gè)非常重要的緩存問(wèn)題,即在每一級(jí)查找時(shí),都會(huì)先查找緩存,然后才去查找方法列表。找到之后,也會(huì)在緩存里邊存一份(即使是在父類(lèi)的類(lèi)對(duì)象或元類(lèi)對(duì)象里邊找到的,也要始終緩存在當(dāng)前類(lèi)對(duì)象或元類(lèi)對(duì)象里),以便提高查找效率。

特例

注意觀察上邊那張關(guān)系圖的右上角,就會(huì)發(fā)現(xiàn),基類(lèi)的元類(lèi)對(duì)象的 superclass 指針指向了自己的類(lèi)對(duì)象,真實(shí)情況是這樣的嗎?我們來(lái)做一個(gè)實(shí)驗(yàn):給 NSObject 添加一個(gè)對(duì)象方法,代碼如下:

@interface NSObject (Extern)

- (void)doInstanceWork;

@end

@implementation NSObject (Extern)

- (void)doInstanceWork {
    NSLog(@"這是 NSObject 的對(duì)象方法");
}

@end

然后,在 main.m 中這樣調(diào)用:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [HHStaff doInstanceWork];
    }
    return 0;
}

即調(diào)用 HHStaff 的類(lèi)方法 +doInstanceWork,不過(guò) HHStaff 里邊并沒(méi)有這個(gè)類(lèi)方法,但是運(yùn)行時(shí)并沒(méi)有報(bào)錯(cuò),控制臺(tái)輸出如下:

2019-02-03 16:09:38.454099+0800 HHH[2667:925051] 這是 NSObject 的對(duì)象方法

也就是說(shuō),確實(shí)如關(guān)系圖所示,執(zhí)行了基類(lèi)的類(lèi)對(duì)象里邊存儲(chǔ)的對(duì)象方法??梢赃@么來(lái)理解,OC 的方法調(diào)用經(jīng)編譯后都會(huì)轉(zhuǎn)成這樣的函數(shù)調(diào)用:objc_msgSend(object, @selector(methodName)) ,這里并沒(méi)有指明是類(lèi)方法還是對(duì)象方法,也就是不關(guān)心是對(duì)象方法還是類(lèi)方法,如果 object 是實(shí)例對(duì)象,就會(huì)去類(lèi)對(duì)象里查找方法,如果 object 是類(lèi)對(duì)象,就會(huì)去元類(lèi)對(duì)象里邊查找。

4.Class 的結(jié)構(gòu)

前邊我們說(shuō)過(guò),類(lèi)中的方法、屬性、協(xié)議等重要信息都存儲(chǔ)在類(lèi)對(duì)象元類(lèi)對(duì)象里邊,這兩者的結(jié)構(gòu)相同,都是 Class 類(lèi)型的,而 Class 的結(jié)構(gòu)實(shí)際就是 struct objc_class,因此我們的目的就是要弄清楚 struct objc_class 的結(jié)構(gòu)。

在 objc 源碼的 objc-runtime-new.h 中找到了 objc_class 的最新定義:

struct objc_class : objc_object {
    // Class ISA;              // isa 不再放這里
    Class superclass;
    cache_t cache;             // 1.緩存
    
    class_data_bits_t bits;    
    class_rw_t *data() {       // 2.class_rw_t
        return bits.data();
    }
    
    // *** 此處略去好多行 O(∩_∩)O~
}

既然 C++ 的結(jié)構(gòu)體是可以繼承的,那么我們來(lái)看看它繼承的結(jié)構(gòu)體 objc_object 里邊都有什么:

struct objc_object {
private:
    isa_t isa;                // 3.isa,注意是私有
public: 
    // 此方法返回的不是 tagged pointer 對(duì)象
    Class ISA();
    // 此方法返回的可能是一個(gè) tagged pointer 對(duì)象
    Class getIsa();
    // *** 此處又略去好多行 O(∩_∩)O~
}

以上就是 objc_class 的表層結(jié)構(gòu),下面針對(duì)其中的 3 各主要部分做一個(gè)相對(duì)深入點(diǎn)的討論。

4.1 cache_t

cache_t 就是前文提到的方法緩存,其結(jié)構(gòu)如下所示(做了適當(dāng)精簡(jiǎn)):

struct cache_t {
    struct bucket_t *_buckets;  // 散列表
    mask_t _mask;               // 散列表的長(zhǎng)度 - 1
    mask_t _occupied;           // 已經(jīng)緩存的方法數(shù)量

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    
    // *** 此處又略去好多行 O(∩_∩)O~
    
    // 擴(kuò)展空間
    void expand();
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    // 查詢(xún)緩存
    struct bucket_t * find(cache_key_t key, id receiver);

    // *** 此處略去好多行 O(∩_∩)O~
};

cache_t 里邊有一個(gè)散列表(哈希表)_buckets,里邊是一個(gè)個(gè)的 struct bucket_t,用于緩存方法。bucket_t 的結(jié)構(gòu)如下所示:

struct bucket_t {
private:
    cache_key_t _key;   // 用 SEL 做 key
    IMP _imp;           // 函數(shù)的內(nèi)存地址 做 value

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};

現(xiàn)在,我們看一下如何查詢(xún)緩存,即 find() 函數(shù)的實(shí)現(xiàn):

bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);    // 根據(jù) k 與 m 算出一個(gè)下標(biāo):begin = k & m
    mask_t i = begin;
    do {                                // 根據(jù)下標(biāo)取值,并驗(yàn)證做了一個(gè)異常處理,即不同 key 得到相同下標(biāo)的問(wèn)題
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

查詢(xún)的基本邏輯是:

  • 先根據(jù)傳入的 k(即key) 和 m(即mask) 算出一個(gè)下標(biāo) begin = k & m

  • 然后用這個(gè)下標(biāo) begin 去散列表里取值,用取到的值 (bucket) 里邊的 key 與 傳入的 k 作比較,

    • 如果相等,就將取到的值 (bucket) 返回;

    • 如果不等,利用 cache_next() 函數(shù) (如下) 算出一個(gè)新的下標(biāo),再去取值比較;

      #if __arm__  ||  __x86_64__  ||  __i386__  // 各種模擬器
      static inline mask_t cache_next(mask_t i, mask_t mask) {
          return (i+1) & mask;
      }
      
      #elif __arm64__                             // 64bits 真機(jī)
      static inline mask_t cache_next(mask_t i, mask_t mask) {
          // 如果 i 不為 0,則返回 i-1;否則返回 mask
          return i ? i-1 : mask;
      }
      
      #else
      #error unknown architecture
      #endif
      
    • 如此循環(huán),最后如果新算出來(lái)的下標(biāo)等于 begin,則退出循環(huán),說(shuō)明緩存里沒(méi)有對(duì)應(yīng)的方法。

4.2 class_rw_t

class_rw_t 是通過(guò) bit 的 data() 函數(shù)獲取的,從名稱(chēng)可以看出來(lái),它是可讀可寫(xiě)的(rw),其基本結(jié)構(gòu)及說(shuō)明如下:

struct class_rw_t {
    
    // *** 此處又略去好多行 O(∩_∩)O~

    const class_ro_t *ro;

    method_array_t methods;         // 方法列表
    property_array_t properties;    // 屬性列表
    protocol_array_t protocols;     // 協(xié)議列表
    
    // *** 此處又略去好多行 O(∩_∩)O~
}

4.2.1 class_ro_t

上邊的 class_rw_t 里有一個(gè) readonly 的 class_ro_t *ro,class_ro_t 的結(jié)構(gòu)及各元素的說(shuō)明如下:

struct class_ro_t {
    
    // *** 此處略去好多行 O(∩_∩)O~
    
    const char * name;                  // 類(lèi)名
    method_list_t * baseMethodList;     // 方法列表
    protocol_list_t * baseProtocols;    // 協(xié)議列表
    const ivar_list_t * ivars;          // 成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;    // 屬性列表

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

class_ro_t 里邊存放的是編譯完成時(shí)類(lèi)結(jié)構(gòu)里邊的方法、屬性、協(xié)議及成員變量等信息。class_rw_t 里邊是在運(yùn)行時(shí)擴(kuò)展了累的方法、屬性等信息以后的結(jié)構(gòu),比如,分類(lèi)中的方法就是加到了這個(gè)結(jié)構(gòu)里。

class_ro_t 里邊有成員變量,而且是只讀的,但 class_rw_t 里沒(méi)有,這也解釋了為什么不能通過(guò)分類(lèi)添加成員變量。當(dāng)然分類(lèi)里是可以添加屬性的,只不過(guò)這樣添加的屬性只能生成 setter 和 getter 的聲明,還需要自己設(shè)法完成他們的實(shí)現(xiàn),至于如何實(shí)現(xiàn),下一篇 runtimen 會(huì)講到。

4.2.2 method_t

后面的幾篇還會(huì)用到 '方法' 相關(guān)的底層概念 method_t,這里就簡(jiǎn)單說(shuō)明一下。上文的源碼中與 method_t 關(guān)系最近的應(yīng)該要數(shù) method_list_t 了,現(xiàn)在我們就通過(guò)它探究一下 method_t,下邊是其源碼:

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    
    bool isFixedUp() const;
    void setFixedUp();

    // 函數(shù):用于獲取具體 method_t 的下標(biāo) index
    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        assert(i < count);
        return i;
    }
};

從源碼能夠看出來(lái), method_list_t 繼承自 entsize_list_tt,后者的定義如下,不過(guò)這里略去了定義,重點(diǎn)看上方的說(shuō)明部分:

/***********************************************************************
* entsize_list_tt<Element, List, FlagMask>
* Generic implementation of an array of non-fragile structs. // 一種xxx結(jié)構(gòu)體數(shù)組的實(shí)現(xiàn)
*
* Element is the struct type (e.g. method_t)
* List is the specialization of entsize_list_tt (e.g. method_list_t)
* FlagMask is used to stash extra bits in the entsize field
*   (e.g. method list fixup markers)
**********************************************************************/
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
    //...
}

從注釋我們了解到,模板中的 List 是由一個(gè)個(gè)的 Element 組成的數(shù)組,結(jié)合前邊 method_list_t 的定義可知,method_list_t 是由 method_t 組成的數(shù)組,method_t 的結(jié)構(gòu)如下:

struct method_t {
    SEL name;          // 名字
    const char *types; // 類(lèi)型
    MethodListIMP imp; // 指針

    //...
};

method_t 的結(jié)構(gòu)中主要包含3個(gè)元素:①方法名 name、②方法類(lèi)型 char *types、③指向方法實(shí)現(xiàn)的指針 imp。

注:property_tprotocol_t 與此類(lèi)似,就不多做介紹了,詳見(jiàn) 代碼注釋。

4.3 isa_t

4.3.1 isa_t

objc_object 這個(gè)結(jié)構(gòu)體里邊 isa 的類(lèi)型是個(gè)共用體 union isa_t ,其結(jié)構(gòu)如下(其中 struct {...} 的作用只是讓各位的含義可視化):

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
    
#if defined(ISA_BITFIELD)   // 位域
    struct {
        ISA_BITFIELD;       // defined in isa.h
    };
#endif
};

從 64 位架構(gòu)開(kāi)始引入了位域,可以在isa 中存儲(chǔ)更多信息,上邊結(jié)構(gòu)體中的 ISA_BITFIELD 定義如下:

// isa.h
# if __arm64__      // 64位真機(jī)
#   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
# elif __x86_64__   // 64位模擬器·  
#   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
# else
#   error unknown architecture for packed isa
# endif

下面這張圖以 64 位真機(jī)為例,詳細(xì)說(shuō)明了各位的作用:

isa位域.png

前邊 3.2 說(shuō)過(guò),從 64 位架構(gòu)開(kāi)始,需要通過(guò) isa.bits & ISA_MASK 才能得到對(duì)應(yīng)類(lèi)對(duì)象或元類(lèi)對(duì)象的地址,其實(shí)就是為了取出 shiftcls 部分。

在前文 struct objc_object 的結(jié)構(gòu)中,我們發(fā)現(xiàn) isa 是私有的,外部只能通過(guò) ISA()getIsa() 這兩個(gè)方法訪問(wèn),下面分別看一下 ISA() 的源碼。

#if SUPPORT_NONPOINTER_ISA

inline Class objc_object::ISA()
{
    // 如果是 TaggedPointer 就會(huì)中斷言
    assert(!isTaggedPointer());  

#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK); 
#endif
}

上邊是 ISA() 的源碼,其中條件編譯的條件是 SUPPORT_INDEXED_ISA,定義如下:

// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa field as an index into a class table.
#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

其中,_ _ ARM_ARCH_7K _ _ 是 Apple Watch 會(huì)用到的,LP64 指的是 Long Pointer 64位,現(xiàn)在絕大多數(shù) Unix 平臺(tái)均使用 LP64 數(shù)據(jù)模型,所以一般情況下 SUPPORT_INDEXED_ISA 的值為 0,也就是說(shuō) ISA() 會(huì)執(zhí)行 else 中的代碼,即 isa.bits & ISA_MASK。

4.3.2 Tagged Pointer

我們注意到 getIsa() 的如下說(shuō)明,也就是說(shuō),getIsa() 允許當(dāng)前對(duì)象(this)是一個(gè) Tagged Pointer 對(duì)象。那么,下面我們就來(lái)了解一下這個(gè)東東。

getIsa() allows this to be a tagged pointer object.

Tagged Pointer 是蘋(píng)果在發(fā)布 iPhone 5s(搭載 64 位架構(gòu)的 A7處理器)時(shí)提出的,它的優(yōu)勢(shì)在于,對(duì)于小對(duì)象(如NSNumber、NSDate等)能夠大大地節(jié)省內(nèi)存和提高執(zhí)行效率。

我們以下面這行代碼為例,比較一下引入 Tagged Pointer 前后占用內(nèi)存的變化。

NSNumber *num = @(2);

如下圖所示,當(dāng)從 32 位機(jī)器遷移到 64 位機(jī)器后,如果沒(méi)有引入 Tagged Pointer,雖然邏輯未改變,但是所占用的內(nèi)存會(huì)翻倍;如果引入了 Tagged Pointer,因?yàn)榇鎯?chǔ) NSNumber 變量(此處為 @(2))本身的值常常用不了 8 個(gè)字節(jié),于是會(huì)將一個(gè)對(duì)象的指針(8 個(gè)字節(jié))拆成兩部分,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記,標(biāo)記是否是 “特殊指針”。

當(dāng)然,這也是有一定限制的,當(dāng) 8 個(gè)字節(jié)可以承載要表示的數(shù)值時(shí),系統(tǒng)就會(huì)以 Tagged Pointer 的方式生成指針;如果 8 個(gè)字節(jié)承載不了,則又會(huì)用以前的方式來(lái)生成普通的指針。

5.小結(jié)

關(guān)于 Class 的討論就先討論到這里,可能有些地方理解的還不是很到位,后邊會(huì)及時(shí)更新的 O(∩_∩)O~

# 參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,068評(píng)論 0 9
  • 本文基于對(duì)象的實(shí)現(xiàn)原理來(lái)深入剖析 OC 的底層相關(guān)原理。這里并不會(huì)簡(jiǎn)單的介紹純理論知識(shí),而是借助工具和編碼實(shí)現(xiàn)相關(guān)...
    steveyoung閱讀 1,117評(píng)論 2 5
  • 首先說(shuō)明,這篇文章幾乎都是抄錄的別人的博客,簡(jiǎn)書(shū)文章,在此總結(jié),只是為了方便記憶和以后閱讀,如果有什么失禮的地方,...
    LiYaoPeng閱讀 5,340評(píng)論 1 14
  • 概述 常說(shuō)Objective-C是一門(mén)動(dòng)態(tài)語(yǔ)言,那么問(wèn)題來(lái)了,這個(gè)動(dòng)態(tài)表現(xiàn)在那些方面呢? 其實(shí)最主要的表現(xiàn)就是Ob...
    Jack_lin閱讀 2,515評(píng)論 2 36
  • 首先這是篇譯文,為了更易理解,個(gè)別地方稍作調(diào)整。如有不正之處,歡迎指出!原文在此 這篇文章中,我將著眼于 OC中一...
    Mad_Mark閱讀 7,264評(píng)論 10 65

友情鏈接更多精彩內(nèi)容