runtime源碼中的類和對(duì)象

本文基于objc4-709源碼進(jìn)行分析。關(guān)于源碼編譯:objc - 編譯Runtime源碼objc4-706

objc中的類和對(duì)象

1.類和對(duì)象的結(jié)構(gòu)概要

NSObject是所有類的基類,NSObject在源碼中的定義:

在NSObject.mm文件中找到

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject類的第一個(gè)成員變量就是Class類型的isa疗绣。

Object.mm文件:

typedef struct objc_class *Class; //類
typedef struct objc_object *id; 

@interface Object { 
    Class isa; 
} 

Class就是c語言定義的objc_class結(jié)構(gòu)體類型的指針歉眷,objc中的類實(shí)際上就是objc_class。

而id類型就是objc_object結(jié)構(gòu)體類型的指針(就是我們平時(shí)經(jīng)常用到的id類型)抒巢,我們平時(shí)用的id可以用來聲明一個(gè)對(duì)象,說明objc中的對(duì)象實(shí)際上就是objc_object热幔。

objc-runtime-new.h

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;           //父類的指針
    cache_t cache;             // formerly cache pointer and vtable 方法緩存
    class_data_bits_t bits;    // class
    ...
}

objc_class繼承于objc_object,objc中的類也是一個(gè)對(duì)象晓殊。

objc-private.h

struct objc_object {
private:
    isa_t isa;//objc_object唯一成員變量
public:
    Class ISA();
    Class getIsa();
    void initIsa(Class cls /*nonpointer=false*/);
    
    ...
private:
    void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
}
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    
    ...
}

objc_object是objc中對(duì)象的定義断凶。isa 是 objc_object的唯一成員變量。我們經(jīng)常說巫俺,所有的objc對(duì)象都包含一個(gè)isa指針认烁,從源碼上來看,現(xiàn)在準(zhǔn)確說應(yīng)該是isa_t結(jié)構(gòu)體(isa指針應(yīng)該是isa_t聯(lián)合體中的Class cls指針)介汹。當(dāng)objc為一個(gè)對(duì)象分配內(nèi)存却嗡,初始化實(shí)例變量后,在這些對(duì)象的實(shí)例變量的結(jié)構(gòu)體中的第一個(gè)就是isa嘹承。

為了方便閱讀窗价,我把objc_class寫成:

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;           //父類的指針
    cache_t cache;             // formerly cache pointer and vtable 方法緩存
    class_data_bits_t bits;    // class
    ...
}

在objc中,對(duì)象的方法的實(shí)現(xiàn)不會(huì)存儲(chǔ)在每個(gè)對(duì)象的結(jié)構(gòu)體中叹卷,而是在相應(yīng)的類里(如果每一個(gè)對(duì)象都要維護(hù)一個(gè)實(shí)例方法列表撼港,那么開銷太大了)。當(dāng)一個(gè)實(shí)例方法被調(diào)用時(shí)骤竹,會(huì)通過對(duì)象的isa帝牡,在對(duì)應(yīng)的類中找到方法的實(shí)現(xiàn)(具體是在class_data_bits_t結(jié)構(gòu)體中查找,里面有一個(gè)方法列表)蒙揣。

同時(shí)我們還從源碼中看到靶溜,objc_class結(jié)構(gòu)體中還有一個(gè)Class類型的superclass成員變量,指向了父類懒震。通過這個(gè)指針可以查找從父類繼承的方法罩息。

然而,對(duì)于類對(duì)象來說个扰,它的isa又是什么呢瓷炮?objective-c里面有一種叫做meta class元類的東西。


對(duì)象锨匆、類崭别、元類關(guān)系圖

為了讓我們能夠調(diào)用類方法,類的isa“指針”必須指向一個(gè)類結(jié)構(gòu)恐锣,并且該類結(jié)構(gòu)必須包含我們可以調(diào)用的類方法列表。這就導(dǎo)致了元類的定義:元類是類對(duì)象的類舞痰。

類方法調(diào)用時(shí)土榴,通過類的isa“指針”在元類中獲取方法的實(shí)現(xiàn)。元類中存儲(chǔ)了一個(gè)類的所有類方法响牛。

從上圖中總結(jié)以下幾個(gè)信息:

  • root class(class)就是NSObject玷禽,由于它是基類赫段,所以它沒有父類。
  • 實(shí)例對(duì)象的isa指向其類矢赁,類的isa指向其元類糯笙。每個(gè)元類的isa都指向根元類root class(meta),根元類的isa指向自己撩银。
  • 根元類的父類指針指向基類(NSObject)给涕。

What is a meta-class in Objective-C?

ps:isa指針并不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來確定類型额获,而應(yīng)用class方法够庙。(KVO的實(shí)現(xiàn)中,將被觀察的對(duì)象的isa指針指向一個(gè)中間類而非真實(shí)的類)抄邀。

2.isa_t結(jié)構(gòu)體的分析

通過源碼耘眨,我們可以知道isa_t實(shí)際上是一個(gè)union聯(lián)合體。其中的方法境肾、成員變量剔难、結(jié)構(gòu)體公用一塊空間。取決于其中的結(jié)構(gòu)體奥喻,最終isa_t共占64位內(nèi)存空間

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;//指針
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

//-------------------arm64上的實(shí)現(xiàn)
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL//ISA_MAGIC_MASK 和 ISA_MASK 分別是通過掩碼的方式獲取MAGIC值 和 isa類指針
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;    //0:普通isa指針偶宫,1:優(yōu)化的指針用于存儲(chǔ)引用計(jì)數(shù)
        uintptr_t has_assoc         : 1;    //表示該對(duì)象是否包含associated object,如果沒有衫嵌,析構(gòu)時(shí)會(huì)更快
        uintptr_t has_cxx_dtor      : 1;    //表示對(duì)象是否含有c++或者ARC的析構(gòu)函數(shù)读宙,如果沒有,析構(gòu)更快
        uintptr_t shiftcls          : 33;   // MACH_VM_MAX_ADDRESS 0x1000000000 類的指針
        uintptr_t magic             : 6;    //用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化(用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間)
        uintptr_t weakly_referenced : 1;    //對(duì)象是否有過weak對(duì)象
        uintptr_t deallocating      : 1;    //是否正在析構(gòu)
        uintptr_t has_sidetable_rc  : 1;    //該對(duì)象的引用計(jì)數(shù)值是否過大無法存儲(chǔ)在isa指針
        uintptr_t extra_rc          : 19;   //存儲(chǔ)引用計(jì)數(shù)值減一后的結(jié)果楔绞。對(duì)象的引用計(jì)數(shù)超過 1结闸,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10酒朵,extra_rc的值就為 9桦锄。
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

//-------------------__x86_64__上的實(shí)現(xiàn)
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        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;  //值為0x3b
        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

#endif

...略

由于源碼要在mac os下才能編譯,因此接下來都基于x86_64分析蔫耽,arm64其實(shí)大同小異结耀。

源碼中遇到的編譯宏的說明:

SUPPORT_PACKED_ISA:表示平臺(tái)是否支持在 isa 指針中插入除 Class 之外的信息。如果支持就會(huì)將 Class 信息放入 isa_t 定義的 struct 內(nèi)(shiftcls)匙铡,并附上一些其他信息图甜,比如引用計(jì)數(shù),析構(gòu)狀態(tài)鳖眼;如果不支持黑毅,那么不會(huì)使用 isa_t 內(nèi)定義的 struct,這時(shí) isa_t 只使用 cls成員變量(Class 指針钦讳,經(jīng)常說的“isa指針”就是這個(gè))矿瘦。在 iOS 以及 MacOSX 上枕面,SUPPORT_PACKED_ISA 定義為 1(支持)。

struct結(jié)構(gòu)體的成員含義:

參數(shù)                     含義
nonpointer      0 表示普通的 isa 指針缚去,1 表示使用優(yōu)化潮秘,存儲(chǔ)引用計(jì)數(shù)
has_assoc       表示該對(duì)象是否包含 associated object 關(guān)聯(lián)對(duì)象,如果沒有易结,則析構(gòu)時(shí)會(huì)更快
has_cxx_dtor        表示該對(duì)象是否有 C++ 或 ARC 的析構(gòu)函數(shù)枕荞,如果沒有,則析構(gòu)時(shí)更快
shiftcls        類的指針
magic           __x86_64__環(huán)境初始化值為 0x3b衬衬,用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化买猖。
weakly_referenced   表示該對(duì)象是否有過 weak 對(duì)象,如果沒有滋尉,則析構(gòu)時(shí)更快
deallocating        表示該對(duì)象是否正在析構(gòu)
has_sidetable_rc    表示該對(duì)象的引用計(jì)數(shù)值是否過大無法存儲(chǔ)在 isa 指針
extra_rc        存儲(chǔ)引用計(jì)數(shù)值減一后的結(jié)果玉控。19位將保存對(duì)象的引用計(jì)數(shù),這樣對(duì)引用計(jì)數(shù)的操作只需要原子的修改這個(gè)指針即可狮惜,如果引用計(jì)數(shù)超出19位高诺,才會(huì)將引用計(jì)數(shù)保存到外部表,而這種情況往往是很少的碾篡,因此效率將會(huì)大大提高虱而。

在初始化一節(jié)會(huì)對(duì)其中一些參數(shù)繼續(xù)談?wù)劇?/p>

初始化

在我們調(diào)用alloc來實(shí)例化對(duì)象時(shí),會(huì)調(diào)用到objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 方法开泽。方法的具體實(shí)現(xiàn):

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{     
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
//__x86_64__和arm64會(huì)進(jìn)入這個(gè)條件分支
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

initInstanceIsa方法實(shí)際上調(diào)用了initIsa方法牡拇,并且傳入nonpointer=true參數(shù)。

由于isa_t是一個(gè)聯(lián)合體穆律,所以newisa.bits = ISA_MAGIC_VALUE;把isa_t中的struct結(jié)構(gòu)體初始化為:

objc-isa-isat-bits.png

實(shí)際上只設(shè)置了nonpointer和magic的值惠呼。

nonpointer

nonpointer 表示是否對(duì)isa開啟指針優(yōu)化。

先介紹一種稱為Tagged Pointer的技術(shù)峦耘,這是蘋果為64位設(shè)備提出的節(jié)省內(nèi)存和提高執(zhí)行效率的一種優(yōu)化方案剔蹋。

假設(shè)我們要存儲(chǔ)一個(gè)NSNumber對(duì)象,其值是一個(gè)整數(shù)辅髓。正常情況下泣崩,如果這個(gè)整數(shù)只是一個(gè)NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān)洛口,在32位CPU下占4個(gè)字節(jié)矫付,在64位CPU下是占8個(gè)字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān)第焰,一個(gè)指針?biāo)加玫膬?nèi)存在32位CPU下為4個(gè)字節(jié)技即,在64位CPU下也是8個(gè)字節(jié)。

如果沒有Tagged Pointer對(duì)象樟遣,從32位機(jī)器遷移到64位機(jī)器中后而叼,雖然邏輯沒有任何變化,但這種NSNumber豹悬、NSDate一類的對(duì)象所占用的內(nèi)存會(huì)翻倍葵陵。

為了改進(jìn)上面提到的內(nèi)存占用和效率問題,蘋果提出了Tagged Pointer對(duì)象瞻佛。由于NSNumber脱篙、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個(gè)字節(jié),拿整數(shù)來說伤柄,4個(gè)字節(jié)所能表示的有符號(hào)整數(shù)就可以達(dá)到20多億绊困。所以我們可以將一個(gè)對(duì)象的指針拆成兩部分,一部分直接保存數(shù)據(jù)适刀,另一部分作為特殊標(biāo)記秤朗,表示這是一個(gè)特別的指針,不指向任何一個(gè)地址笔喉。

技術(shù)詳細(xì)可以查看以下兩個(gè)鏈接取视。

深入理解Tagged Pointer

64位與Tagged Pointer

雖然Tagged Pointer專門用來存儲(chǔ)小的對(duì)象,例如NSNumber和NSDate常挚。但在isa指針優(yōu)化上用到了tagged Pointer的概念作谭。

使用整個(gè)指針大小的內(nèi)存來存儲(chǔ)isa有些浪費(fèi),在arm64上運(yùn)行的iOS只用了33位(和結(jié)構(gòu)體中shiftcls的33無關(guān)奄毡,Mac OS用了47位)折欠,剩下的31位用于其他目的。

nonpointer = 0 吼过,raw isa锐秦,表示isa_t不使用struct結(jié)構(gòu)體,訪問對(duì)象的 isa 會(huì)直接返回 cls 指針那先,cls 會(huì)指向?qū)ο笏鶎俚念惖慕Y(jié)構(gòu)农猬。這是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型。

nonpointer = 1售淡,isa_t使用了struct結(jié)構(gòu)體斤葱,此時(shí) isa 不再是指針,不能直接訪問 objc_object 的 isa 成員變量揖闸,但是其中也有 cls 的信息揍堕,只是其中關(guān)于類的指針都是保存在 shiftcls 中。

bool hasNonpointerIsa();這個(gè)函數(shù)就是用來判斷當(dāng)前對(duì)象的isa是否啟用tagged pointer的汤纸。

magic

用于判斷當(dāng)前對(duì)象是否已經(jīng)完成初始化衩茸。

has_cxx_dtor

接著設(shè)置has_cxx_dtor,這一位表示當(dāng)前對(duì)象是否有 C++ 或者 ObjC 的析構(gòu)器贮泞,如果沒有析構(gòu)器就會(huì)快速釋放內(nèi)存楞慈。

shiftcls

newisa.shiftcls = (uintptr_t)cls >> 3; 將對(duì)象對(duì)應(yīng)的類的指針存入結(jié)構(gòu)體的shiftcls成員中幔烛。

將cls(地址)右移三位的原因:字節(jié)對(duì)齊。類的指針按照8字節(jié)對(duì)齊囊蓝。

為什么需要字節(jié)對(duì)齊

C語言字節(jié)對(duì)齊問題詳解

對(duì)象的內(nèi)存地址必須對(duì)齊到字節(jié)的倍數(shù)饿悬,這樣可以提高代碼運(yùn)行的性能,在 iPhone5s 中虛擬地址為 33 位聚霜,所以用于對(duì)齊的最后三位比特為 000狡恬,我們只會(huì)用其中的 30 位來表示對(duì)象的地址。

將cls右移三位蝎宇,可以將地址中無用的后三位清除減小內(nèi)存的消耗弟劲。

回過頭來看,類的isa的shiftcls就指向元類了姥芥。

ISA()

由于開啟了指針優(yōu)化后兔乞,isa不再是指針,要獲取類指針就要用到 ISA() 方法撇眯。

#define ISA_MASK 0x00007ffffffffff8ULL

inline Class 
objc_object::ISA() 
{
#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); //按位與運(yùn)算獲取類指針报嵌,__x86_64__是44位
#endif
}

3.cache_t 結(jié)構(gòu)體的分析

typedef unsigned int uint32_t;
typedef uint32_t mask_t; 
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    ......
};

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
    ......
};

cache 存放著實(shí)例方法的緩存,提高方法調(diào)用效率熊榛。當(dāng)一個(gè)對(duì)象調(diào)用一個(gè)方法時(shí)锚国,它會(huì)先從緩存里面找這個(gè)方法,如果沒有找到才會(huì)去類的方法列表中去找玄坦。

如果一個(gè)對(duì)象調(diào)用一個(gè)方法血筑,首先根據(jù)對(duì)象的isa找到對(duì)應(yīng)的類,再在類的方法列表中尋找這個(gè)方法煎楣,如果找不到就到父類中的方法列表查找豺总,一旦找到就調(diào)用。如果沒有找到择懂,有可能轉(zhuǎn)發(fā)消息喻喳,也可能忽略它。但這樣效率太低了困曙,有些方法會(huì)經(jīng)常用到表伦,那么每次調(diào)用都要走一遍以上流程,是不是很慢慷丽?用cache來緩存方法蹦哼,優(yōu)先在 cache 中查找,找到就調(diào)用沒找到再走正常路子要糊。
cache_t中存儲(chǔ)了一個(gè)bucket_t結(jié)構(gòu)體指針和兩個(gè)無符號(hào)整形變量纲熏。

bucket_t結(jié)構(gòu)體中存儲(chǔ)了IMP函數(shù)指針和unsigned long。_buckets 用來存儲(chǔ)Method的鏈表。

_mask 分配用來緩存bucket的總數(shù)局劲。

_occupied 當(dāng)前占用的bucket數(shù)勺拣。

4.class_data_bits_t

之前說過,實(shí)例方法被調(diào)用時(shí)容握,會(huì)通過其持有 isa 指針尋找對(duì)應(yīng)的類宣脉,然后在其中的 class_data_bits_t 中查找對(duì)應(yīng)的方法。相對(duì)來說 class_data_bits_t 會(huì)有點(diǎn)復(fù)雜剔氏,包含了 class_rw_t 、class_ro_t 等重要的結(jié)構(gòu)體竹祷。這一節(jié)會(huì)對(duì) class_data_bits_t 進(jìn)行分析谈跛,介紹方法是如何在objc中存取的。

struct class_data_bits_t {

    uintptr_t bits;
    
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
};

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 這個(gè)類的實(shí)例方法鏈表

    class_rw_t *data() { 
        return bits.data();
    }
    ......
};

class_data_bits_t 結(jié)構(gòu)體只含有一個(gè)64bit的bits變量塑陵,它存儲(chǔ)了類相關(guān)的信息感憾,和不同的以'FAST_'開頭的flag掩碼做按位與運(yùn)算。

uintptr_t bits存儲(chǔ)了一個(gè)指向 class_rw_t 結(jié)構(gòu)體的指針和三個(gè)標(biāo)志位令花。bits在64位兼容版中的內(nèi)容:

// 當(dāng)前類是swift類
#define FAST_IS_SWIFT           (1UL<<0)
//當(dāng)前類或者父類含有默認(rèn)的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// 當(dāng)前類的實(shí)例需要 raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// 數(shù)據(jù)指針
#define FAST_DATA_MASK          0x00007ffffffffff8UL
class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

在 objc_class 的 data 方法中阻桅,直接調(diào)用并返回了 class_data_bits_t 的data方法。bits 和 FAST_DATA_MASK做按位與運(yùn)算兼都,轉(zhuǎn)換為 class_rw_t *并返回嫂沉。

Mac OS X 只使用 47 位內(nèi)存地址,所以bits前 17 位空余出來扮碧。bits中最大的一塊存儲(chǔ)區(qū)域用于存儲(chǔ)class_rw_t 指針趟章,在 FAST_DATA_MASK 對(duì)應(yīng)的[3,46]位數(shù)據(jù)段中。[3,46]一共是44位慎王,有沒有覺得44很眼熟蚓土?由于字節(jié)對(duì)齊,所以47位的后三位為0赖淤,可以用來做標(biāo)志位蜀漆,所以就有了另外三個(gè)標(biāo)志位。

除了 FAST_DATA_MASK 是用一段空間做存儲(chǔ)外咱旱,其他宏都是用1bit确丢。這些數(shù)據(jù)都有對(duì)應(yīng)的getter、setter封裝函數(shù)莽龟。比如:

bool isSwift() {return getBit(FAST_IS_SWIFT);}

void setIsSwift() {setBits(FAST_IS_SWIFT);}

bool hasDefaultRR() {return getBit(FAST_HAS_DEFAULT_RR);}

void setHasDefaultRR() {setBits(FAST_HAS_DEFAULT_RR);}

在64非兼容版本下還有更多的宏定義蠕嫁,位于objc-runtime-new.h文件中,如有需要請(qǐng)自行閱讀源碼毯盈。

class_rw_t

從上面的分析中我們知道剃毒,class_data_bits_t 用了大段空間存儲(chǔ)了 class_rw_t 指針。class_rw_t 提供了運(yùn)行時(shí)對(duì)類擴(kuò)展的能力,class_rw_t 結(jié)構(gòu)體中保存了 objc 中的屬性赘阀、方法益缠、協(xié)議等信息。

在 objc_class結(jié)構(gòu)體中的注釋寫到 class_data_bits_t相當(dāng)于 class_rw_t指針加上 rr/alloc 的標(biāo)志基公。

class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods; //方法
    property_array_t properties;//屬性
    protocol_array_t protocols;//協(xié)議

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;//是計(jì)算機(jī)語言用于解決實(shí)體名稱唯一性的一種方法幅慌,做法是向名稱中添加一些類型信息,用于從編譯器中向連接器傳遞更多語義信息轰豆。
    
    ......
};

關(guān)于32位 flags 標(biāo)志胰伍,在源碼中有這樣一段注釋:

Values for class_rw_t->flags
These are not emitted by the compiler and are never used in class_ro_t.
Their presence should be considered in future ABI versions.

它的值不是由編譯器設(shè)置的,并且從不在class_ro_t中使用酸休。未來的ABI版本應(yīng)考慮到他們的存在骂租。

flags 標(biāo)記了類的一些狀態(tài),與生命周期斑司、內(nèi)存管理有關(guān)渗饮,有些位目前還沒有定義。


class_rw_t 中的 methods宿刮、properties互站、protocols 存放了和類的方法、屬性僵缺、協(xié)議有關(guān)的信息胡桃。

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

class method_array_t : public list_array_tt<method_t, method_list_t> 
class property_array_t : public list_array_tt<property_t, property_list_t> 
class protocol_array_t : public list_array_tt<protocol_ref_t, protocol_list_t> 

method_array_t、property_array_t谤饭、protocol_array_t這三種類都繼承自 list_array_tt<Element, List>這種結(jié)構(gòu)标捺。

template <typename Element, typename List>
class list_array_tt {
    struct array_t {
        uint32_t count;
        List* lists[0];//長度為0的數(shù)組,c99的寫法,允許在運(yùn)行期動(dòng)態(tài)申請(qǐng)內(nèi)存
    };
}

list_array_tt 存儲(chǔ)一些元數(shù)據(jù),是通過c++模板定義的容器類答毫,提供了一些諸如count年鸳、迭代器iterator的方法和類。Element表示元數(shù)據(jù)的類型,比如 method_t 、 property_t 、 protocol_ref_t屋谭;List表示存儲(chǔ)元數(shù)據(jù)的容器,理解成是用于存儲(chǔ)元數(shù)據(jù)的一維數(shù)組龟糕,比如 method_list_t 桐磁、 property_list_t 。由于list_array_tt 存儲(chǔ)了List指針數(shù)組讲岁,所以list_array_tt實(shí)際上可以看做是元數(shù)據(jù)的二維數(shù)組我擂。list_array_tt 有三種狀態(tài):

  • 自身為空
  • List指針數(shù)組只有一個(gè)指向元數(shù)據(jù)數(shù)組的指針
  • List指針數(shù)組有多個(gè)指針

一個(gè)類創(chuàng)建之初可能處于前兩個(gè)狀態(tài)衬以,如果用category或者class_addMethod來添加方法,就變成第三個(gè)狀態(tài)校摩,而且是不可逆的回不去前兩個(gè)狀態(tài)

可以對(duì)list_array_tt不斷進(jìn)行擴(kuò)張看峻。比如在通過category添加方法時(shí),就調(diào)用到這個(gè)方法衙吩,把新的方法列表(相當(dāng)于是裝有一個(gè)category所有方法的容器)添加到二維數(shù)組中:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

     //調(diào)用realloc將原空間擴(kuò)展互妓,把原數(shù)組復(fù)制到后面,新數(shù)組復(fù)制到前面
    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    
    //List指針數(shù)組只有一個(gè)指針時(shí)
    //malloc重新申請(qǐng)內(nèi)存坤塞,最后一個(gè)位置留給原來元數(shù)據(jù)數(shù)組的指針
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

無論是哪一種邏輯冯勉,新加的方法列表都會(huì)添加到二維數(shù)組前面。

有一個(gè)名字很類似的結(jié)構(gòu)體 class_ro_t尺锚,'rw' 和 ro' 相信很容易就理解是 'readwrite' 和 'readonly'的意思吧珠闰。

class_rw_t 的結(jié)構(gòu)體成員中還有一個(gè)指向常量的指針class_ro_t *ro。因此在編譯期間類的結(jié)構(gòu)中的 class_data_bits_t *data 指向的是一個(gè) class_ro_t * 指針瘫辩。class_ro_t也是一個(gè)結(jié)構(gòu)體,下一節(jié)我們來講講它坛悉。

class_ro_t

class_rw_t 的內(nèi)容可以在運(yùn)行時(shí)動(dòng)態(tài)修改伐厌,而 class_ro_t 存儲(chǔ)了在類編譯時(shí)就確定的信息,比如屬性裸影、方法挣轨、協(xié)議、成員變量轩猩。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//類名
    method_list_t * baseMethodList;//方法列表
    protocol_list_t * baseProtocols;//協(xié)議列表
    const ivar_list_t * ivars;//ivar列表

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

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

在類進(jìn)行初始化之前卷扮,通過object_class 的data()方法得到的實(shí)際是一個(gè)指向 class_ro_t 結(jié)構(gòu)體的指針。

instanceStart均践、instanceSize 兩個(gè)成員變量的存在保證了objc2.0的ABI穩(wěn)定性晤锹。Non Fragile ivars

method_list_t、ivar_list_t彤委、property_list_t 結(jié)構(gòu)體都繼承自 entsize_list_tt<Element, List, FlagMask> , protocol_list_t 的結(jié)構(gòu)則相對(duì)簡單很多鞭铆。entsize_list_tt 是通過c++模板定義的容器類,提供了一些諸如count焦影、get车遂、迭代器iterator的方法和類,通過這些方法和類可以方便地遍歷并獲取容器內(nèi)的數(shù)據(jù)斯辰。

template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;//總大小
    uint32_t count;//個(gè)數(shù)
    Element first;//第一個(gè)元數(shù)據(jù)
};

而在類進(jìn)行初始化之時(shí)舶担,會(huì)調(diào)用到一個(gè) static Class realizeClass(Class cls) 方法(這個(gè)之后分析oc中的消息發(fā)送時(shí)會(huì)具體分析,這里只要知道會(huì)調(diào)用到這個(gè)方法即可)彬呻。

realizeClass的實(shí)現(xiàn)節(jié)選:

ro = (const class_ro_t *)cls->data();//強(qiáng)制轉(zhuǎn)換
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);//初始化一個(gè) class_rw_t 結(jié)構(gòu)體,分配可讀寫數(shù)據(jù)空間
    rw->ro = ro;//設(shè)置結(jié)構(gòu)體 ro 的值以及 flag
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);//最后設(shè)置正確的 data
}

這里主要做了幾個(gè)事:

  • 調(diào)用 object_class 的 data 方法衣陶,把得到的 class_rw_t 指針強(qiáng)制轉(zhuǎn)換成 class_ro_t 指針柄瑰。
  • 新初始化一個(gè) class_rw_t ,分配可讀寫數(shù)據(jù)空間祖搓。
  • 將 class_ro_t 指針賦值給 class_rw_t->ro 狱意,并設(shè)置 class_rw_t->flag 。
  • 設(shè)置 object_class 的正確 data 拯欧。

也即由以下圖1變成了圖2:圖片來源深入解析 ObjC 中方法的結(jié)構(gòu)


但是此時(shí) class_rw_t 中的方法详囤,屬性以及協(xié)議列表均為空。最后在 realizeClass 方法中 調(diào)用了 methodizeClass 方法來將類 class_ro_t 的方法镐作、屬性和遵循的協(xié)議加載到 methods藏姐、 properties 和 protocols 列表中(用到了在 class_rw_t 一節(jié)最后提到的 attachLists 方法),但是class_ro_t其中的 ivar 不會(huì)被拷貝(不能在運(yùn)行時(shí)給已有的類增加實(shí)例變量)该贾。methodizeClass 方法節(jié)選:

// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
    prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
    rw->methods.attachLists(&list, 1);
}

property_list_t *proplist = ro->baseProperties;
if (proplist) {
    rw->properties.attachLists(&proplist, 1);
}

protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
    rw->protocols.attachLists(&protolist, 1);
}

可以這樣說羔杨,經(jīng)過realizeClass函數(shù)處理的類才成為了真正的類。

5.驗(yàn)證一下

一下這part的內(nèi)容基于Draveness大大《深入解析 ObjC 中方法的結(jié)構(gòu)》一文中驗(yàn)證部分進(jìn)行復(fù)現(xiàn)杨蛋,分析運(yùn)行時(shí)初始化過程的內(nèi)存變化兜材,作為自己敲一下代碼所做的記錄。

#import <Foundation/Foundation.h>
@interface XXObject : NSObject
@property (nonatomic, assign) CGFloat test;

- (void)hello;
@end


#import "XXObject.h"
@implementation XXObject
- (void)hello {
    NSLog(@"Hello");
}
@end

主程序代碼:
#import <Foundation/Foundation.h>
#import "XXObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class cls = [XXObject class];
        NSLog(@"%p", cls);
    }
    return 0;
}

由于類在內(nèi)存中的位置是編譯期就確定的逞力,在之后修改代碼曙寡,也不會(huì)改變內(nèi)存中的位置。所以先跑一次代碼獲取XXObject 在內(nèi)存中的地址寇荧。地址為:0x1000011e0

在運(yùn)行時(shí)初始化之前加入斷點(diǎn):


然后通過lldb驗(yàn)證一下:

(lldb) p (objc_class *)0x1000011e0
(objc_class *) $0 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x100001200 //偏移32為獲取class_data_bits_t指針
(class_data_bits_t *) $1 = 0x0000000100001200
(lldb) p $1->data()
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(class_rw_t *) $2 = 0x0000000100001158
(lldb) p (class_ro_t *)$2   //強(qiáng)制轉(zhuǎn)換類型
(class_ro_t *) $3 = 0x0000000100001158
(lldb) p *$3
(class_ro_t) $4 = {
  flags = 128
  instanceStart = 8
  instanceSize = 16
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f6e "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001118
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001140
}

可以看到name举庶、baseMethodList、ivars揩抡、baseProperties是有值的户侥,對(duì)于后兩者有值的原因:因?yàn)閄XObject類中我添加了@property (nonatomic, assign) CGFloat test;屬性,而屬性也在編譯期自動(dòng)生成了一個(gè)對(duì)應(yīng)的ivar(帶下劃線前綴的原屬性名的成員變量)峦嗤。

//獲取ivars
(lldb) p $4.ivars
(const ivar_list_t *) $6 = 0x0000000100001118
(lldb) p *$6
//顯示count=1只有一個(gè)ivar
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 1
    first = {
      offset = 0x00000001000011b0
      name = 0x0000000100000f8b "_test"
      type = 0x0000000100000fb2 "d"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x00000001000011b0
  name = 0x0000000100000f8b "_test"
  type = 0x0000000100000fb2 "d"
  alignment_raw = 3
  size = 8
}

//獲取property
(lldb) p $4.baseProperties
(property_list_t *) $10 = 0x0000000100001140
(lldb) p *$10
(property_list_t) $11 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "test", attributes = "Td,N,V_test")
  }
}

查看 baseMethodList 中的內(nèi)容:

(lldb) p $4.baseMethodList
(method_list_t *) $14 = 0x00000001000010c8
(lldb) p *$14
(method_list_t) $15 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 3
    first = {
      name = "hello"
      types = 0x0000000100000f97 "v16@0:8"
      imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
    }
  }
}
(lldb) p $15.get(0)
(method_t) $16 = {
  name = "hello"
  types = 0x0000000100000f97 "v16@0:8"
  imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
}
(lldb) p $15.get(1)
(method_t) $17 = {
  name = "test"
  types = 0x0000000100000f9f "d16@0:8"
  imp = 0x0000000100000e50 (debug-objc`-[XXObject test] at XXObject.h:11)
}
(lldb) p $15.get(2)
(method_t) $18 = {
  name = "setTest:"
  types = 0x0000000100000fa7 "v24@0:8d16"
  imp = 0x0000000100000e70 (debug-objc`-[XXObject setTest:] at XXObject.h:11)
}

結(jié)果顯示 baseMethodList 中有三個(gè)方法蕊唐,打印出來看,第一個(gè)是 XXObject 的 hello 方法寻仗,其余兩個(gè)是 test 屬性的對(duì)應(yīng)getter和setter方法刃泌。所以也就不難理解為什么objc中的property = ivar + getter + setter了。

關(guān)于"v16@0:8"署尤、"d16@0:8"耙替、"v24@0:8d16" 請(qǐng)見Type Encoding

以上這些都是在編譯器就生成了的。

接著來看一下如果沒有把class_rw_t強(qiáng)轉(zhuǎn)成class_ro_t時(shí)的情形:先打印出來留到后面與初始化之后進(jìn)行對(duì)比曹体。

(lldb) p *$2
(class_rw_t) $5 = {
  flags = 128
  version = 8
  ro = 0x0000000000000010
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100000f6e
        arrayAndFlag = 4294971246
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x00000001000010c8
        arrayAndFlag = 4294971592
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100001118
  demangledName = 0x0000000000000000 <no value available>
}

接著在 realizeClass 方法中的這個(gè)地方打一個(gè)斷點(diǎn):(直接和地址值進(jìn)行比較是因?yàn)轭愒诰幾g期就確定了在內(nèi)存中的地址)


判斷當(dāng)前類是否XXObject俗扇,此時(shí)還沒有進(jìn)行初始化,類結(jié)構(gòu)體中的布局依舊是上面那樣沒有變化的箕别。

[圖片上傳失敗...(image-5a0ad-1517664314598)]
當(dāng)這段代碼運(yùn)行完后铜幽,再來lldb打印一下:

(lldb) p (objc_class *)0x1000011e0
(objc_class *) $19 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x0000000100001200
(class_data_bits_t *) $20 = 0x0000000100001200
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000100f05030
(lldb) p *$21
(class_rw_t) $22 = {
  flags = 2148007936
  version = 0
  ro = 0x0000000100001158
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = nil
  demangledName = 0x0000000000000000 <no value available>
}
(lldb) p $22.ro
(const class_ro_t *) $23 = 0x0000000100001158
(lldb) p *$23
(const class_ro_t) $24 = {
  flags = 128
  instanceStart = 8
  instanceSize = 16
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f6e "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100001118
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001140
}

可以看到滞谢,$3class_ro_t * 已經(jīng)被設(shè)置成$22 class_rw_t中的 ro 成員了,但 class_rw_t 的 methods除抛、properties狮杨、protocols 成員
仍然為空。

等到執(zhí)行完 methodizeClass 方法:


(lldb) p *$21
(class_rw_t) $25 = {
  flags = 2148007936
  version = 0
  ro = 0x0000000100001158
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000010c8
        arrayAndFlag = 4294971592
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100001140
        arrayAndFlag = 4294971712
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fffcd723f50
  demangledName = 0x0000000000000000 <no value available>
}

class_ro_t 中 baseMethodList 中的方法被添加到 class_rw_t 中的 methods 數(shù)組中到忽,class_ro_t 中 baseProperties 被添加到 class_rw_t 中的 properties橄教。核心都是通過在 methodizeClass 方法中執(zhí)行 attachLists 方法添加類的方法、屬性喘漏、協(xié)議:

auto rw = cls->data();
auto ro = rw->ro;

// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
    prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
    rw->methods.attachLists(&list, 1);
}

property_list_t *proplist = ro->baseProperties;
if (proplist) {
    rw->properties.attachLists(&proplist, 1);
}

protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
    rw->protocols.attachLists(&protolist, 1);
}

總結(jié)

  • 類的方法护蝶、屬性以及協(xié)議在編譯期間存放到了“錯(cuò)誤”的位置,直到 realizeClass 執(zhí)行之后翩迈,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t持灰,這樣我們即可以在運(yùn)行時(shí)為 class_rw_t 添加方法,也不會(huì)影響類的只讀結(jié)構(gòu)负饲。

  • 在 class_ro_t 中的屬性在運(yùn)行期間就不能改變了堤魁,再添加方法時(shí),會(huì)修改 class_rw_t 中的 methods 列表返十,而不是 class_ro_t 中的 baseMethodList

6.對(duì)象的初始化

創(chuàng)建一個(gè) NSObject 對(duì)象的代碼:[[NSObject alloc] init];

alloc 方法的實(shí)現(xiàn):

+ (id)alloc {
    return _objc_rootAlloc(self);
}

僅僅調(diào)用了一個(gè)私有方法_objc_rootAlloc

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    // 檢查 cls 信息是否為 nil姨涡,如果為 nil,則無法創(chuàng)建新對(duì)象吧慢,返回 nil。
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    //是否有自定義的allocWithZone方法
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        //沒有alloc / allocWithZone實(shí)現(xiàn)赏表。
        
        //是否可以快速分配內(nèi)存检诗。這里好像是寫死了返回false
        if (fastpath(cls->canAllocFast())) {
            bool dtor = cls->hasCxxDtor();//是否有析構(gòu)器
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            id obj = class_createInstance(cls, 0);// 創(chuàng)建對(duì)象的關(guān)鍵函數(shù)
            if (slowpath(!obj)) return callBadAllocHandler(cls);// 分配失敗
            return obj;
        }
    }
#endif

    // callAlloc 傳入 allocWithZone = true
    if (allocWithZone) return [cls allocWithZone:nil];// 這里 cls 的 allocWithZone 方法里也是調(diào)用了 class_createInstance。
    return [cls alloc];
}

關(guān)于slowpath瓢剿、fastpath兩個(gè)宏逢慌,實(shí)際上是__builtin_expect(),一個(gè)GCC的一個(gè)內(nèi)建函數(shù)间狂,用于分支預(yù)測(cè)優(yōu)化攻泼。__builtin_expect — 分支預(yù)測(cè)優(yōu)化。只需要知道if(fastpath(x))if(x)是相同意思就可以了鉴象。

實(shí)際上我在執(zhí)行[[XXObject alloc] init]這句代碼時(shí)忙菠,if (fastpath(!cls->ISA()->hasCustomAWZ()))并沒有進(jìn)入這個(gè)條件分支:

直接跳到這一步了

所以也即相當(dāng)于:

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    return [cls allocWithZone:nil];
}

但很神奇的是,接下來并沒有執(zhí)行allocWithZone方法(打了斷點(diǎn)但無事發(fā)生)纺弊,這是一個(gè)讓我覺得很迷的地方牛欢。最后落入的是這兩個(gè)方法:

id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();// 是否有構(gòu)造函數(shù)
    bool hasCxxDtor = cls->hasCxxDtor();// 是否有析構(gòu)函數(shù)
    bool fast = cls->canAllocNonpointer();// 是否使用原始 isa 格式

    size_t size = cls->instanceSize(extraBytes);// 需要分配的空間大小,從 instanceSize 實(shí)現(xiàn)可以知道對(duì)象至少16字節(jié)
        if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    
    if (!zone  &&  fast) {//跑代碼時(shí)會(huì)執(zhí)行這個(gè)分支
        obj = (id)calloc(1, size);//分配空間
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);//看到了一個(gè)熟悉的方法~
    } 
    else { //這里不執(zhí)行
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

在上面函數(shù)的實(shí)現(xiàn)中淆游,執(zhí)行了 objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 方法傍睹。在第二部分講過隔盛,這個(gè)方法就是用來初始化isa_t結(jié)構(gòu)體的。

初始化 isa 之后拾稳,[NSObject alloc] 的工作算是做完了吮炕,下面就是 init 相關(guān)邏輯:

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}

可以看到, init 實(shí)際上只是返回了當(dāng)前對(duì)象访得。

總結(jié)一下對(duì)象的初始化過程主要做了兩個(gè)事:1.分配需要的內(nèi)存空間 2.初始化isa_t結(jié)構(gòu)體龙亲。

參考文章:

從 NSObject 的初始化了解 isa

深入解析 ObjC 中方法的結(jié)構(gòu)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市震鹉,隨后出現(xiàn)的幾起案子俱笛,更是在濱河造成了極大的恐慌,老刑警劉巖传趾,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迎膜,死亡現(xiàn)場離奇詭異,居然都是意外死亡浆兰,警方通過查閱死者的電腦和手機(jī)磕仅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來簸呈,“玉大人榕订,你說我怎么就攤上這事⊥杀悖” “怎么了劫恒?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轿腺。 經(jīng)常有香客問我两嘴,道長,這世上最難降的妖魔是什么族壳? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任憔辫,我火速辦了婚禮,結(jié)果婚禮上仿荆,老公的妹妹穿的比我還像新娘贰您。我一直安慰自己,他們只是感情好拢操,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布锦亦。 她就那樣靜靜地躺著,像睡著了一般庐冯。 火紅的嫁衣襯著肌膚如雪孽亲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天展父,我揣著相機(jī)與錄音返劲,去河邊找鬼玲昧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篮绿,可吹牛的內(nèi)容都是我干的孵延。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼亲配,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼尘应!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吼虎,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤犬钢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后思灰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玷犹,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年洒疚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歹颓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡油湖,死狀恐怖巍扛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乏德,我是刑警寧澤撤奸,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站喊括,受9級(jí)特大地震影響寂呛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘾晃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幻妓。 院中可真熱鬧蹦误,春花似錦、人聲如沸肉津。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妹沙。三九已至偶洋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間距糖,已是汗流浹背玄窝。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工牵寺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恩脂。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓帽氓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俩块。 傳聞我的和親對(duì)象是個(gè)殘疾皇子黎休,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容