Runtime原理探究

Runtime簡介


運行時最主要的是消息機制

  • 對于C語言彰触,函數(shù)調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)
  • 對于OC函數(shù)命辖,屬于 動態(tài)調(diào)用過程,在編譯的時候并不能決定真正調(diào)用哪個函數(shù)尔艇,只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用,
  • 在編譯階段味廊,oc可以調(diào)用任何函數(shù)棠耕,即使這個函數(shù)并未實現(xiàn)余佛,只要聲明過就不會報錯
    在編譯階段窍荧,C語言調(diào)用未實現(xiàn)的函數(shù)就會報錯
  • 如果向某個對象傳遞消息,在底層所有的方法都是普通的C語言函數(shù)红氯,然而對象收到消息之后,究竟該調(diào)用哪個方法則完全取決于運行期決定,甚至可能在運行期改變喇嘱,這些特性使得OC變成一門真正的動態(tài)語言
  • 在Runtime中,對象可以使用C語言中的結(jié)構(gòu)體表示腔丧,而方法可以用C函數(shù)實現(xiàn)作烟,另外在加上了額外的特性愉粤,這些結(jié)構(gòu)體和函數(shù)被Runtime函數(shù)封裝后拿撩,讓OC的面向?qū)ο缶幊套優(yōu)榭赡?/li>

Objective-C中的數(shù)據(jù)結(jié)構(gòu)


1.id

運行時系統(tǒng)如何知道某個對象的類型呢?對象類型并不是在編譯期就知道了影暴,而是要在運行期查找,OC有個特殊類型id,它可以表示OC的任意對象類型型宙,id類型定義在Runtime的頭文件中:

struct obje_object {
          Class isa;
} *id;

由此可見,每個對象結(jié)構(gòu)體的首個成員變量是Class類的isa妆兑,該變量定義了對象所屬的類,通常指isa指針

objc_object

objc_object 是一個表示一個類實例的結(jié)構(gòu)體芯勘,它的定義如下(objc/objc.h):

struct objc_object{
           Class isa OBJC_ISA_AVAILABILITY;
};
typedef struce objc_object *id;

可以看到谱姓,這個結(jié)構(gòu)體只有一個實體借尿,基指向其類的isa指針屉来。這樣,當我們向一個OC對象發(fā)送消息時茂契,運行時庫會根據(jù)實例對象的isa指針找到這個實例對象所屬的類,Runtime庫會在類的方法列表以及父類的方法列表中尋找與消息對應的selector指向的方法掉冶,找到后即運行這個方法脐雪。

2.Class

Class對象也定義在Runtime的頭文件中,查看objc/runtime.h中的objc_class結(jié)構(gòu)體:Objective-c中战秋,類是由Class類型來表示的,它實際是一個指向objc_class結(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;//類的版本信息,默認為0
    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;//協(xié)議鏈表
#endif

} OBJC2_UNAVAILABLE;

下面說下Class 結(jié)構(gòu)體的幾個主要變量:
1.isa:結(jié)構(gòu)體的首個變量也是isa指針埋泵,這說明Class本身也是OC中的對象。isa指針非常重要,對象需要通過isa指針找到它的類规阀,類需要isa找到元類瘦麸,這在調(diào)用實例方法和類方法的時候起到重要作用谁撼。
2.super_class:結(jié)構(gòu)體里還有個變量是super_class,它定義了本類的超類滋饲。類對象所屬類型(isa指針所指向的類型)是另一個類,叫做元類箍鼓。
3.ivars:成員變量列表,類的成員都在ivars里面款咖。
4.methodLists:方法列表奄喂,類的實例方法都在methodLists里铐殃,類方法在元類的methodLists里面跨新。methodLists是一個指針的指針,通過修改該指針指向指針的值赘被,就可以動態(tài)的為某一個類添加成員方法肖揣。這就是Category實現(xiàn)的原理民假,同時也說明Category只可以為對象添加成員方法龙优,不能添加成員變量。
5.cache:方法緩存列表陋率,objc_msgSend(下文詳解)每調(diào)用一次方法后,就會把該方法緩存到cache列表中瓦糟,下次調(diào)用的時候赴蝇,會優(yōu)先從cache列表中尋找菩浙,如果cache沒有,才從methodLists中查找方法陆淀,提高效率先嬉。

元類(Meta Class)

meta-class 是一個類對象的類轧苫。在上面我們提到疫蔓,所有的類自身也是一個對象,我們可以向這個對象發(fā)送消息(即調(diào)用類方法)岔乔。既然是對象,那么它也是一個objc_object指針雏门,它包含一個指向其類的一個isa指針掸掏,那么,這個isa指針指向什么呢阅束?為了調(diào)用類方法,這個類的isa指針必須指向一個包含這個類方法的一個objc_class結(jié)構(gòu)體息裸。這就引出了meta-class的概念,meta-class中存儲著一個類的所有類方法年扩。所以,調(diào)用類方法的這個類對象的isa指針指向的就是meta-class 當我們向一個對象發(fā)送消息時厨幻,runtime會在這個對象所屬的這個類的方法列表中查找方法腿时;而向一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找批糟。

再深入一下,meta-class 也是一個類徽鼎,也可以向它發(fā)送一個消息弹惦,那么它的isa又是指向什么呢悄但?為了不讓這種結(jié)構(gòu)無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為他們的所屬類檐嚣。

即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類报咳,而基類的meta-class的isa指針是指向它自己挖藏。

通過上面的描述暑刃,再加上對objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應meta-class類的一個繼承體系了岩臣,如下代碼

image.png

上圖 superclass指針代表繼承關系宵膨,isa指針代表實例所屬的類。類也是一個對象辟躏,它是另一個類的實例,這個就是”元類“会涎,元類里面保存了類方法的列表,類里面保存了實例方法的列表末秃。實例對象的isa指向類籽御,類對象的isa指向元類,元類對象的isa指向一個根元類(root metaclass)技掏。所有子類的元類都繼承父類的元類,換而言之哑梳,類對象和元類對象有同樣的繼承關系。

Class是一個指向objc_class結(jié)構(gòu)體的指針哪工,而id是一個指向objc_object結(jié)構(gòu)體的指針,其中的isa是一個指向objc_class結(jié)構(gòu)體的指針雁比。其中的id就是我們說的對象撤嫩,Class就是我們所說的類。isa指針不總是指向?qū)嵗龑ο笏鶎俚念愋蛉粒荒芤揽克鼇泶_定類型,而是應該用isKindOfClass:方法來確定實例對象的類程奠。因為KVO的實現(xiàn)機制就是將被觀察對象的isa指針指向一個中間類而不是真實的類。

Category

Category是表示一個指向分類的結(jié)構(gòu)體的指針己沛,其定義如下:

/// An opaque type that represents a category.
typedef struct objc_category *Category;
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}  

這個結(jié)構(gòu)體主要包含了分類定義的實例方法與類方法距境,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集垫桂。可發(fā)現(xiàn)诬滩,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實例變量和屬性

struct objc_ivar_list *ivars             OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表

3.SEL

  • 方法交換(method swizzing)
    在Objctive-C中蒙挑,對象收到消息后愚臀,究竟會調(diào)用哪種方法需要在運行期才能解析出來忆蚀。查找消息的唯一依據(jù)是選擇子(selector),選擇子(selector)與相應的方法(IMP)對應姑裂,利用Objective-C的動態(tài)特性,可以實現(xiàn)在運行時偷換選擇子(selector)對應的方法實現(xiàn)欣鳖,這就是方法交換 (method swizzing).
    每個類都有一個方法列表茴厉,存放著selector的名字和方法實現(xiàn)的映射關系泽台。IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn)稻爬。


    類的方法列表會把每個選擇子都映射到相關的IMP之上

    image.png

我們可以新增選擇子蜕依,也可以改變某個選擇子所對應的IMP,還可以交換兩個選擇子所映射到的指針样眠。

  • Objective-C中提供了三種API來動態(tài)替換類方法或?qū)嵗椒ǖ膶崿F(xiàn):
    1.class_replaceMethod替換類方法的定義。
class_replaceMethod(Class cls,SEL name,IMP imp,const char *types)

2.method_exchangeImplementations交換兩個方法的實現(xiàn)辫秧。

method_exchangeImplementations(Method m1,Method m2)

3.method_setImplementation設置一個方法的實現(xiàn)

method_setImplementation(Method m,IMP imp)

先說這三個方法的區(qū)別:

  • class_replaceMethod:當類中沒有想替換的原方法時厢塘,該方法調(diào)用class_addMethod來為類增加一個新方法茶没,也正因如此晚碾,class_replaceMethod在調(diào)用時需傳入types參數(shù),而其余兩個缺不需要笛求。
  • method_exchangeImplementations: 內(nèi)部實現(xiàn)就是調(diào)用了兩次method_setImplemetation方法糕簿。

再來看看他們的使用場景:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSelector = @selector(willMoveToSuperview:);
        SEL swizzledSelector = @selector(myWillMoveToSuperview:);

        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        
        BOOL didAddMethod = class_addMethod(self, 
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(self, 
                                swizzledSelector, 
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
    NSLog(@"WillMoveToSuperview: %@", self); 
    [self myWillMoveToSuperview:newSuperview];
}

總結(jié)

class_replaceMethod,當需要替換的方法有可能不存在是,可以考慮使用該方法懂诗。
method_exchangeImplementations,當需要交換兩個方法時使用
method_setImplementation是最簡單的用法植旧,當僅僅需要為一個方法設置其實現(xiàn)方式時實現(xiàn)。

4.Ivar

ivar 代表類中實例變量的類型病附,在Runtime的頭文件中的定義如下:
typedef struct objc_ivar *Ivar;
objc_ivar的定義如下:

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

class_copyIvarList(Class cls,unsigned int *outCount)可以使用這個方法獲取某個類的成員變量列表

5.objc_property_t

objc_property_t是屬性亥鬓,在Runtime的頭文件中的定義如下:
typedef struct objc_property *objc_property_t;
class_copyPropertyList(Class cls, unsigned int *outCount) 可以使用這個方法獲取某個類的屬性列表。

Runtime原理探究

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末覆积,一起剝皮案震驚了整個濱河市听皿,隨后出現(xiàn)的幾起案子宽档,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿短,死亡現(xiàn)場離奇詭異,居然都是意外死亡昔逗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門婆排,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔链,“玉大人,你說我怎么就攤上這事鉴扫。” “怎么了坪创?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柠掂。 經(jīng)常有香客問我依沮,道長涯贞,這世上最難降的妖魔是什么悉抵? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮傻谁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘审磁。我一直安慰自己,他們只是感情好态蒂,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著手素,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泉懦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天崩哩,我揣著相機與錄音言沐,去河邊找鬼。 笑死险胰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的起便。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鸟悴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了细诸?” 一聲冷哼從身側(cè)響起陋守,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎水评,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體中燥,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年拿霉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绽淘。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖壮池,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情火窒,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站离钝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏卵渴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一浪读、第九天 我趴在偏房一處隱蔽的房頂上張望辛藻。 院中可真熱鬧,春花似錦吱肌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规揪。三九已至,卻和暖如春猛铅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祥款。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刃跛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓桨昙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親齐苛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345