iOS中的runtime和OC對(duì)象的底層結(jié)構(gòu)

首先我們來創(chuàng)建一個(gè)類,然后再創(chuàng)建這個(gè)類的一個(gè)對(duì)象
創(chuàng)建一個(gè)父類Person

@interface Person : NSObject 
{
int _age;
int _no;
int _height;
}

-(void)personInstanceMethod;
+(void)personClassMethod;

@end

@implementation Person

- (void)personInstanceMethod{
    
}

+ (void)personClassMethod{
    
}
@end

創(chuàng)建Person的子類Student烤芦,為什么要這樣創(chuàng)建举娩,方便后面講清楚對(duì)象與類,類與父類构罗,類與元類之間的關(guān)系

@interface Student : Person
{
    int _sex;
}

-(void)studentInstanceMethod;
+(void)studentClassMethod;

@end

@implementation Student

- (void)studentInstanceMethod{
    
}

+ (void)studentClassMethod{
    
}
@end

然后我們創(chuàng)建一個(gè)Student的對(duì)象

Student *student = [[Student alloc]init];
[student personInstanceMethod];

在OC中每個(gè)對(duì)象都是一個(gè)結(jié)構(gòu)體铜涉,結(jié)構(gòu)體中都包含一個(gè)isa的成員變量,其位于成員變量的第一位遂唧。isa的成員變量之前都是Class類型的芙代,后來蘋果將其改為isa_t

struct objc_object {
private:
    isa_t isa;
};

上面是對(duì)象的結(jié)構(gòu)體蠢箩,你們發(fā)現(xiàn)里面只有一個(gè)isa指針,下面我們來看看類的結(jié)構(gòu)體
查看網(wǎng)上相關(guān)資料事甜,我們會(huì)發(fā)現(xiàn)出現(xiàn)了兩種結(jié)構(gòu)體
第一種

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;
/* Use `Class` instead of `struct objc_class *` */

但是看里面的相關(guān)代碼會(huì)發(fā)現(xiàn)有個(gè)前置條件#if !__OBJC2__谬泌,而我們現(xiàn)在基本上用的都是objective-c 2.0,所以上面的結(jié)構(gòu)體只能反映以前的結(jié)構(gòu)
下面是新的結(jié)構(gòu)體

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是繼承于objc_object的,因此objc_class中也包含isa_t類型的isaobjc_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
}

對(duì)象與類仲闽,類與父類马胧,類與元類之間的關(guān)系

了解完內(nèi)部結(jié)構(gòu)后,我們就可以進(jìn)一步來闡述對(duì)象與類滋将,類與父類邻悬,類與元類之間的關(guān)系,其中元類和類采用的是同一種數(shù)據(jù)結(jié)構(gòu)
其中isa必須指向一個(gè)地方随闽,而superclass當(dāng)沒有父類的時(shí)候可以為nil

關(guān)系指向圖

對(duì)象里面的isa指向自己的類父丰,類里面的superclass指向自己的父類,isa指向元類掘宪,通過上面這張圖蛾扇,再來理解下面這張隨處可見的圖攘烛,就容易理解多了,
所以類的關(guān)系圖

到此镀首,我們對(duì)象與類坟漱,類與父類,類與元類的關(guān)系就說清楚了

類中的實(shí)例方法更哄、類方法和屬性的結(jié)構(gòu)

objc_class中芋齿,我們主要研究class_rw_t,rw_tread_write_table的縮寫,意思是可讀可寫的表。bits.data()返回class_rw_t竖瘾,class_rw_t里面存放的什么信息呢?我們點(diǎn)擊進(jìn)入class_rw_t看看:

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é)議信息
}

可以看到我們想看的方法,屬性,協(xié)議信息.只是還沒看到成員變量信息. class_rw_t中有一個(gè)class_ro_t,ro_treadOnly_table的縮寫,意思是只讀的表.我們點(diǎn)擊進(jìn)入const class_ro_t看看里面存放哪些信息:

struct class_ro_t {
    const char * name;//類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成員變量
}

下面一張圖,能一目了然展示Class的內(nèi)存結(jié)構(gòu):

Class的內(nèi)存結(jié)構(gòu)

元類的方法列表中主要是類方法的信息

結(jié)合上面的結(jié)構(gòu)沟突,再講講面試中經(jīng)常被問到的東西

SELSEL(選擇器)是方法的selector的指針。方法的selector表示運(yùn)行時(shí)方法的名字捕传。OC在編譯時(shí)惠拭,會(huì)依據(jù)每一個(gè)方法的名字、參數(shù)庸论,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址)职辅,這個(gè)標(biāo)識(shí)就是SEL

IMPIMP是一個(gè)函數(shù)指針聂示,指向方法最終實(shí)現(xiàn)的首地址域携。SEL就是為了查找方法的最終實(shí)現(xiàn)IMP

Method:用于表示類定義中的方法鱼喉,它的結(jié)構(gòu)體中包含一個(gè)SELIMP秀鞭,相當(dāng)于在SELIMP之間作了一個(gè)映射。

消息機(jī)制:任何方法的調(diào)用本質(zhì)就是發(fā)送一個(gè)消息扛禽。編譯器會(huì)將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個(gè)消息函數(shù)objc_msgSend(receiver, selector)锋边。
objc_msgSend做了如下事情:

  1. 通過對(duì)象的isa指針獲取類的結(jié)構(gòu)體。
  2. 在結(jié)構(gòu)體的方法表里查找方法的selector编曼。
  3. 如果沒有找到selector豆巨,則通過objc_msgSend結(jié)構(gòu)體中指向父類的指針找到父類,并在父類的方法表里查找方法的selector掐场。
  4. 依次會(huì)一直找到NSObject往扔。
  5. 一旦找到selector,就會(huì)獲取到方法實(shí)現(xiàn)IMP熊户。
  6. 傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)萍膛。
  7. 如果最終沒有定位到selector,就會(huì)走消息轉(zhuǎn)發(fā)流程

Runtime的使用:獲取屬性列表嚷堡,獲取成員變量列表卦羡,獲得方法列表,獲取協(xié)議列表,方法交換(黑魔法)绿饵,動(dòng)態(tài)的添加方法欠肾,調(diào)用私有方法,為分類添加屬性拟赊。

1.什么是isa指針刺桃?

isa指針的作用:當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)根據(jù)這個(gè)對(duì)象的isa指針找到這個(gè)對(duì)象所屬的類吸祟,在這個(gè)類的方法列表及父類的方法列表中瑟慈,尋找與消息對(duì)應(yīng)的selector指向的方法,找到后就運(yùn)行這個(gè)方法屋匕。

2.是如何找到IMP的葛碧?
在尋找IMP的地址時(shí),runtime提供了兩種方法

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)

而根據(jù)官方描述过吻,第一種方法可能會(huì)更快一些
對(duì)于第一種方法而言进泼,類方法和實(shí)例方法實(shí)際上都是通過調(diào)用class_getMethodImplementation()來尋找IMP地址的,不同之處在于傳入的第一個(gè)參數(shù)不同類方法(假設(shè)有一個(gè)類A)

class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
類方法當(dāng)中傳入的是元類

實(shí)例方法

class_getMethodImplementation([A class],@selector(methodName));

而對(duì)于第二種方法而言纤虽,傳入的參數(shù)只有Method乳绕,區(qū)分類方法和實(shí)例方法在于封裝Method的函數(shù)
類方法

Method class_getClassMethod(Class cls, SEL name)

實(shí)例方法

Method class_getInstanceMethod(Class cls, SEL name)

最后調(diào)用

IMP method_getImplementation(Method m)

獲取IMP地址

3.消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)最后我們要調(diào)用的方法找不到時(shí),通常我們會(huì)報(bào)一個(gè)“沒有此方法的錯(cuò)誤”逼纸,在報(bào)錯(cuò)之前洋措,我們實(shí)際上會(huì)有三次解決該錯(cuò)誤造成閃退的機(jī)會(huì)

當(dāng)對(duì)象無法接收消息,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制杰刽,通過這一機(jī)制菠发,告訴對(duì)象如何處理未知的消息。

①動(dòng)態(tài)方法解析

對(duì)象接收到未知的消息時(shí)贺嫂,首先會(huì)調(diào)用所屬類的方法

(實(shí)例方法)
+resolveInstanceMethod:

或 者

(類方法)
+resolveClassMethod:

在這個(gè)方法中滓鸠,我們有機(jī)會(huì)為該未知消息新增一個(gè)”處理方法”。使用該“處理方法”的前提是已經(jīng)實(shí)現(xiàn)涝婉,只需要在運(yùn)行時(shí)通過class_addMethod函數(shù)哥力,動(dòng)態(tài)的添加到類里面就可以了蔗怠。

②備用接收者

如果在上一步無法處理消息墩弯,則Runtime會(huì)繼續(xù)調(diào)下面的方法。

- (id)forwardingTargetForSelector:(SEL)aSelector

如果這個(gè)方法返回一個(gè)對(duì)象寞射,則這個(gè)對(duì)象會(huì)作為消息的新接收者渔工。
注意這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無限循環(huán)桥温。如果沒有指定對(duì)象來處理aSelector引矩,則應(yīng)該

return [super forwardingTargetForSelector:aSelector]。

但是我們只將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上,無法對(duì)消息進(jìn)行處理旺韭,例如操作消息的參數(shù)和返回值氛谜。

③完整消息轉(zhuǎn)發(fā)

如果在上一步還是不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制区端。此時(shí)會(huì)調(diào)用以下方法:

- (id)forwardInvocation:(NSInvocation *)anInvocation

這是最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象值漫。創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,把與消息的有關(guān)全部細(xì)節(jié)封裝在anInvocation中织盼,包括selector杨何,目標(biāo)(target)和參數(shù)。在forwardInvocation方法中將消息轉(zhuǎn)發(fā)給其它對(duì)象沥邻。

forwardInvocation:方法的實(shí)現(xiàn)有兩個(gè)任務(wù):

a. 定位可以響應(yīng)封裝在anInvocation中的消息的對(duì)象危虱。

b. 使用anInvocation作為參數(shù),將消息發(fā)送到選中的對(duì)象唐全。anInvocation將會(huì)保留調(diào)用結(jié)果埃跷,runtime會(huì)提取這一結(jié)果并發(fā)送到消息的原始發(fā)送者。

在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能芦瘾,我們可以對(duì)消息的內(nèi)容進(jìn)行修改捌蚊。另外,若發(fā)現(xiàn)消息不應(yīng)由本類處理近弟,則應(yīng)調(diào)用父類的同名方法缅糟,以便繼承體系中的每個(gè)類都有機(jī)會(huì)處理。

另外祷愉,必須重寫下面的方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機(jī)制從這個(gè)方法中獲取信息來創(chuàng)建NSInvocation對(duì)象窗宦。

NSObjectforwardInvocation方法只是調(diào)用了doesNotRecognizeSelector方法,它不會(huì)轉(zhuǎn)發(fā)任何消息二鳄。如果不在以上所述的三個(gè)步驟中處理未知消息赴涵,則會(huì)引發(fā)異常。這里為什么提到NSObject订讼,因?yàn)?code>NSObject是最后的父類髓窜,一直找不到就會(huì)找到NSObject中。

forwardInvocation就像一個(gè)未知消息的分發(fā)中心欺殿,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象寄纵。或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象脖苏,取決于具體的實(shí)現(xiàn)程拭。

消息的轉(zhuǎn)發(fā)機(jī)制可以用下圖來幫助理解。


轉(zhuǎn)發(fā)機(jī)制

4.isa結(jié)構(gòu)體內(nèi)容
isa_t定義
isa_t是一個(gè)union的結(jié)構(gòu)對(duì)象棍潘,union類似于C++結(jié)構(gòu)體恃鞋,其內(nèi)部可以定義成員變量和函數(shù)崖媚。

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

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; // 是32位還是64位
        uintptr_t has_assoc         : 1; // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用,如果沒有關(guān)聯(lián)引用恤浪,可以更快的釋放對(duì)象
        uintptr_t has_cxx_dtor      : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的析構(gòu)函數(shù)
        uintptr_t shiftcls          : 33; // 對(duì)象指向類的內(nèi)存地址畅哑,也就是isa指向的地址
        uintptr_t magic             : 6; // 對(duì)象是否初始化完成
        uintptr_t weakly_referenced : 1; // 對(duì)象是否被弱引用或曾經(jīng)被弱引用
        uintptr_t deallocating      : 1; // 對(duì)象是否被釋放中
        uintptr_t has_sidetable_rc  : 1; // 對(duì)象引用計(jì)數(shù)太大,是否超出存儲(chǔ)區(qū)域
        uintptr_t extra_rc          : 19; // 對(duì)象引用計(jì)數(shù)
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
// ····
# else
// ····
# endif
};

具體例子參考
runtime運(yùn)行時(shí) isa指針 SEL方法選擇器 IMP函數(shù)指針 Method方法 runtime消息機(jī)制 runtime的使用
內(nèi)容有部分參考:
OC對(duì)象的底層結(jié)構(gòu)及isa水由、superClass詳解
探秘Runtime - 剖析Runtime結(jié)構(gòu)體

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敢课,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绷杜,更是在濱河造成了極大的恐慌直秆,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞭盟,死亡現(xiàn)場(chǎng)離奇詭異圾结,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)齿诉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門筝野,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粤剧,你說我怎么就攤上這事歇竟。” “怎么了抵恋?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵焕议,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我弧关,道長(zhǎng)盅安,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任世囊,我火速辦了婚禮别瞭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘株憾。我一直安慰自己蝙寨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布嗤瞎。 她就那樣靜靜地躺著墙歪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猫胁。 梳的紋絲不亂的頭發(fā)上箱亿,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天跛锌,我揣著相機(jī)與錄音弃秆,去河邊找鬼届惋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菠赚,可吹牛的內(nèi)容都是我干的脑豹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼衡查,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瘩欺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拌牲,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤俱饿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后塌忽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拍埠,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年土居,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枣购。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡擦耀,死狀恐怖棉圈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眷蜓,我是刑警寧澤分瘾,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站吁系,受9級(jí)特大地震影響芹敌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垮抗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一氏捞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冒版,春花似錦液茎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至续室,卻和暖如春栋烤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挺狰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工明郭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留买窟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓薯定,卻偏偏與公主長(zhǎng)得像始绍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子话侄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348