【iOS】Runtime底層詳解

一啃匿、Class的本質(zhì)

下列代碼是仿照objc_class結(jié)構(gòu)體晃择,提取其中需要使用到的信息,自定義的一個結(jié)構(gòu)體姆怪。

#import <Foundation/Foundation.h>

#ifndef XXClassInfo_h

#define XXClassInfo_h

# if __arm64__

#   define ISA_MASK        0x0000000ffffffff8ULL

# elif __x86_64__

#   define ISA_MASK        0x00007ffffffffff8ULL

# endif

#if __LP64__

typedef uint32_t mask_t;

#else

typedef uint16_t mask_t;

#endif

typedef uintptr_t cache_key_t;

struct bucket_t {

    cache_key_t _key;

    IMP _imp;

};

struct cache_t {

    bucket_t *_buckets;

    mask_t _mask;

    mask_t _occupied;

};

struct entsize_list_tt {

    uint32_t entsizeAndFlags;

    uint32_t count;

};

struct method_t {

    SEL name;

    const char *types;

    IMP imp;

};

struct method_list_t : entsize_list_tt {

    method_t first;

};

struct ivar_t {

    int32_t *offset;

    const char *name;

    const char *type;

    uint32_t alignment_raw;

    uint32_t size;

};

struct ivar_list_t : entsize_list_tt {

    ivar_t first;

};

struct property_t {

    const char *name;

    const char *attributes;

};

struct property_list_t : entsize_list_tt {

    property_t first;

};

struct chained_property_list {

    chained_property_list *next;

    uint32_t count;

    property_t list[0];

};

typedef uintptr_t protocol_ref_t;

struct protocol_list_t {

    uintptr_t count;

    protocol_ref_t list[0];

};

struct class_ro_t {

    uint32_t flags;

    uint32_t instanceStart;

    uint32_t instanceSize;  // instance對象占用的內(nèi)存空間

#ifdef __LP64__

    uint32_t reserved;

#endif

    const uint8_t * ivarLayout;

    const char * name;  // 類名

    method_list_t * baseMethodList;

    protocol_list_t * baseProtocols;

    const ivar_list_t * ivars;  // 成員變量列表

    const uint8_t * weakIvarLayout;

    property_list_t *baseProperties;

};

struct class_rw_t {

    uint32_t flags;

    uint32_t version;

    const class_ro_t *ro;

    method_list_t * methods;    // 方法列表

    property_list_t *properties;    // 屬性列表

    const protocol_list_t * protocols;  // 協(xié)議列表

    Class firstSubclass;

    Class nextSiblingClass;

    char *demangledName;

};

#define FAST_DATA_MASK          0x00007ffffffffff8UL

struct class_data_bits_t {

    uintptr_t bits;

public:

    class_rw_t *data() { 

        // 提供data()方法進(jìn)行 & FAST_DATA_MASK 操作

        return (class_rw_t *)(bits & FAST_DATA_MASK);

    }

};

/* OC對象 */

struct xx_objc_object {

    void *isa;

};

/* 類對象 */

struct xx_objc_class : xx_objc_object {

    Class superclass;

    cache_t cache;

    class_data_bits_t bits;

public:

    class_rw_t* data() {

        return bits.data();

    }

    // 提供metaClass函數(shù)叛赚,獲取元類對象

    xx_objc_class* metaClass() { 

        // isa指針需要經(jīng)過一次 & ISA_MASK操作之后才得到真正的地址

        return (xx_objc_class *)((long long)isa & ISA_MASK);

    }

};

#endif /* XXClassInfo_h */

根據(jù)結(jié)構(gòu)體中的內(nèi)容及其關(guān)系,總結(jié)如下圖:

image.png

可以看出稽揭,每個類都對應(yīng)有一個class_rw_t結(jié)構(gòu)體俺附,class_rw_t結(jié)構(gòu)體內(nèi)有一個指向class_ro_t結(jié)構(gòu)體的指針。在編譯期間溪掀,class_ro_t結(jié)構(gòu)體就已經(jīng)確定事镣,objc_class中的bits的data部分存放著該結(jié)構(gòu)體的地址。在runtime運行之后揪胃,具體說來是在運行runtime的realizeClass方法時璃哟,會生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t只嚣,并且更新data部分沮稚,換成class_rw_t結(jié)構(gòu)體的地址。細(xì)看兩個結(jié)構(gòu)體的成員變量會發(fā)現(xiàn)很多相同的地方册舞,他們都存放著當(dāng)前類的屬性、實例變量障般、方法调鲸、協(xié)議等等。區(qū)別在于:class_ro_t存放的是編譯期間就確定的挽荡;而class_rw_t是在runtime時才確定藐石,它會先將class_ro_t的內(nèi)容拷貝過去,然后再將當(dāng)前類的分類的這些屬性定拟、方法等拷貝到其中于微。所以可以說class_rw_t是class_ro_t的超集逗嫡,當(dāng)然實際訪問類的方法、屬性等也都是訪問的class_rw_t中的內(nèi)容株依。

二驱证、isa的本質(zhì)

OC對象在內(nèi)存中的排布是一個結(jié)構(gòu)體,其大致框架如下圖:

image.png

每個對象結(jié)構(gòu)體的首個成員是個Class類型的變量,該變量定義了對象所屬的類,通常稱為isa指針恋腕。在arm64位下的iOS操作系統(tǒng)中抹锄,OC對象的isa區(qū)域不再只是一個指針,需要經(jīng)過一次位運算之后才得到真正的地址荠藤。用 64 bit 存儲一個內(nèi)存地址顯然是種浪費伙单,畢竟很少有那么大內(nèi)存的設(shè)備。于是可以優(yōu)化存儲方案哈肖,用一部分額外空間存儲其他內(nèi)容吻育。isa 指針第一位為 1 即表示使用優(yōu)化的 isa 指針,這里列出不同架構(gòu)下的 64 位環(huán)境中 isa 指針結(jié)構(gòu):

union isa_t

{

    isa_t() { }

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

    Class cls;

    uintptr_t bits;

#if SUPPORT_NONPOINTER_ISA

# if __arm64__

#   define ISA_MASK        0x00000001fffffff8ULL

#   define ISA_MAGIC_MASK  0x000003fe00000001ULL

#   define ISA_MAGIC_VALUE 0x000001a400000001ULL

    struct {

        uintptr_t indexed           : 1;

        uintptr_t has_assoc         : 1;

        uintptr_t has_cxx_dtor      : 1;

        uintptr_t shiftcls          : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000

        uintptr_t magic             : 9;

        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  0x0000000000000001ULL

#   define ISA_MAGIC_VALUE 0x0000000000000001ULL

    struct {

        uintptr_t indexed           : 1;

        uintptr_t has_assoc         : 1;

        uintptr_t has_cxx_dtor      : 1;

        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000

        uintptr_t weakly_referenced : 1;

        uintptr_t deallocating      : 1;

        uintptr_t has_sidetable_rc  : 1;

        uintptr_t extra_rc          : 14;

#       define RC_ONE   (1ULL<<50)

#       define RC_HALF  (1ULL<<13)

    };

# else

    // Available bits in isa field are architecture-specific.

#   error unknown architecture

# endif

// SUPPORT_NONPOINTER_ISA

#endif

};

下面是一些位所代表的的含義

截屏2020-06-02 下午4.13.50.png

三淤井、對象扫沼,類對象,元類對象的關(guān)系

image.png

上圖所示即為對象庄吼、類對象缎除、元類對象之間的關(guān)系,總結(jié)如下:

1.每一個對象中都包含一個isa對象总寻。

2.實例的isa指針指向類器罐,類是一個objc_class結(jié)構(gòu)體,包含實例的方法列表渐行、參數(shù)列表轰坊、category等,除此之外祟印,objc_class中還有一個super_class肴沫,指向其類的父類。

3.類的isa指針指向元類蕴忆,即metaClass颤芬,元類存儲類方法等信息。元類里也包含isa指針套鹅,元類里的isa指針指向根元類站蝠,根元類的isa指針指向自己。

4.obj_msgSend發(fā)送實例消息的時候卓鹿,先找到實例菱魔,然后通過實例的isa指針找到類的方法列表及參數(shù)列表等,如果找到則返回吟孙,如果沒有找到澜倦,則通過super_class在其父類中重復(fù)此過程聚蝶。

5.obj_msgSend發(fā)送類消息的時候,通過類的isa找到元類藻治,然后流程與步驟4相同碘勉。

四、消息傳遞機(jī)制

OC是一門非常動態(tài)的語言栋艳,以至于確定調(diào)用哪個方法被推遲到了運行時恰聘,而非編譯時。與之相反吸占,C語言使用靜態(tài)綁定晴叨,也就是說,在編譯期就能決定程序運行時所應(yīng)該調(diào)用的函數(shù)矾屯,所以在C語言中兼蕊,如果某個函數(shù)沒有實現(xiàn),編譯時是不能通過的件蚕。而OC是相對動態(tài)的語言孙技,運行時還可以向類中動態(tài)添加方法,所以編譯時并不能確定方法到底有沒有對應(yīng)的實現(xiàn)排作,編譯器在編譯期間也就不能報錯牵啦。

對象的方法調(diào)用用OC的術(shù)語來講叫做“給某個對象發(fā)送某條消息”。在運行時妄痪,編譯器會把方法調(diào)用轉(zhuǎn)化為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用哈雏,即objc_msgSend(),該函數(shù)是運行時消息傳遞機(jī)制中的核心函數(shù)衫生。

對象的方法調(diào)用步驟如下:

1.實例對象的方法調(diào)用要先通過實例的isa指針找到類裳瘪,隨后去該類的方法 cache 中查找,如果找到了就返回它罪针。

2.如果沒有找到彭羹,就去該類的方法列表中查找。如果在該類的方法列表中找到了泪酱,則將 IMP 返回派殷,并將它加入cache中緩存起來。根據(jù)最近使用原則西篓,這個方法再次調(diào)用的可能性很大愈腾,緩存起來可以節(jié)省下次調(diào)用再次查找的開銷。

3.如果在該類的方法列表中沒找到對應(yīng)的 IMP岂津,再通過該類結(jié)構(gòu)中的 super_class指針在其父類的方法 cache和方法列表中查找。當(dāng)在某個父類的方法 cache或方法列表中找到對應(yīng)的 IMP悦即,就返回它吮成,否則就繼續(xù)循環(huán)橱乱,直到基類。

4.如果在自身以及所有父類的方法 cache和方法列表中都沒有找到對應(yīng)的 IMP粱甫,則進(jìn)入消息轉(zhuǎn)發(fā)流程泳叠。

5.類對象的方法調(diào)用要通過類的isa找到元類,隨后到元類及其所有父類的方法 cache 和方法列表中進(jìn)行查找茶宵,流程與步驟1~4相同危纫。

image.png

五、消息轉(zhuǎn)發(fā)機(jī)制

消息傳遞過程中會在相關(guān)的類對象中搜索方法列表乌庶,如果找不到則會沿著繼承樹向上一直搜索直到繼承樹根部(通常為NSObject)种蝶,如果還是找不到就進(jìn)行消息轉(zhuǎn)發(fā),如果消息轉(zhuǎn)發(fā)失敗了就會執(zhí)行doesNotRecognizeSelector:方法報unrecognized selector錯瞒大。消息轉(zhuǎn)發(fā)主要分三步:動態(tài)方法決議螃征、備用接收者、完整消息轉(zhuǎn)發(fā)透敌,流程如下:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盯滚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酗电,更是在濱河造成了極大的恐慌魄藕,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撵术,死亡現(xiàn)場離奇詭異背率,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荷荤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門退渗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蕴纳,你說我怎么就攤上這事会油。” “怎么了古毛?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵翻翩,是天一觀的道長。 經(jīng)常有香客問我稻薇,道長嫂冻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任塞椎,我火速辦了婚禮桨仿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘案狠。我一直安慰自己服傍,他們只是感情好钱雷,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吹零,像睡著了一般罩抗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灿椅,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天套蒂,我揣著相機(jī)與錄音,去河邊找鬼茫蛹。 笑死操刀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麻惶。 我是一名探鬼主播馍刮,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窃蹋!你這毒婦竟也來了卡啰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤警没,失蹤者是張志新(化名)和其女友劉穎匈辱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杀迹,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡亡脸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了树酪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浅碾。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖续语,靈堂內(nèi)的尸體忽然破棺而出垂谢,到底是詐尸還是另有隱情,我是刑警寧澤疮茄,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布滥朱,位于F島的核電站,受9級特大地震影響力试,放射性物質(zhì)發(fā)生泄漏徙邻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一畸裳、第九天 我趴在偏房一處隱蔽的房頂上張望缰犁。 院中可真熱鬧,春花似錦、人聲如沸民鼓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丰嘉。三九已至,卻和暖如春嚷缭,著一層夾襖步出監(jiān)牢的瞬間饮亏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工阅爽, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留路幸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓付翁,卻偏偏與公主長得像简肴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子百侧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344