Objective-C Runtime到底是什么東西免胃?

Objective-C Runtime 1小時(shí)入門教程

Objective-C Runtime到底是什么東西曹货?

Objective-C Runtime是一個(gè)將C語言轉(zhuǎn)化為面向?qū)ο笳Z言的擴(kuò)展奖亚。

我們將C++和Objective進(jìn)行對比,雖然C++和Objective-C都是在C的基礎(chǔ)上加入面向?qū)ο蟮奶匦詳U(kuò)充而成的程序設(shè)計(jì)語言甘邀,但二者實(shí)現(xiàn)的機(jī)制差異很大琅攘。C++是基于靜態(tài)類型,而Objective-C是基于動態(tài)運(yùn)行時(shí)類型松邪。

也就是說用C++編寫的程序通過編譯器直接把函數(shù)地址硬編碼進(jìn)入可執(zhí)行文件坞琴;而Objective-C無法通過編譯器直接把函數(shù)地址硬編碼進(jìn)入可執(zhí)行文件,而是在程序運(yùn)行的時(shí)候逗抑,利用Runtime根據(jù)條件判斷作出決定剧辐。函數(shù)標(biāo)識與函數(shù)過程的真正內(nèi)容之間的關(guān)聯(lián)可以動態(tài)修改。Runtime是Objective不可缺少的重要一部分邮府。

Objective-C的元素認(rèn)知

id和Class

打開/Public Headers/objc.h文件可以看到如下定義:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

Class是一個(gè)指向objc_class結(jié)構(gòu)體的指針浙于,而id是一個(gè)指向objc_object結(jié)構(gòu)體的指針,其中的isa是一個(gè)指向objc_class結(jié)構(gòu)體的指針挟纱。其中的id就是我們所說的對象羞酗,Class就是我們所說的類。

打開/Public Headers/runtime.h文件
objc_class的定義如下:

typedef struct objc_class *Class;
struct objc_class { 
 Class isa                                 OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
 Class super_class                         OBJC2_UNAVAILABLE; // 父類
 const char *name                          OBJC2_UNAVAILABLE; // 類名
 long version                              OBJC2_UNAVAILABLE; // 類的版本信息紊服,默認(rèn)為0檀轨,可以通過runtime函數(shù)class_setVersion或者class_getVersion進(jìn)行修改胸竞、讀取
 long info                                 OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行時(shí)期使用的一些位標(biāo)識参萄,如CLS_CLASS (0x1L) 表示該類為普通 class卫枝,其中包含實(shí)例方法和變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
 long instance_size                        OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大卸锟妗(包括從父類繼承下來的實(shí)例變量)
 struct objc_ivar_list *ivars              OBJC2_UNAVAILABLE; // 該類的成員變量地址列表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法地址列表校赤,與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L)筒溃,則存儲實(shí)例方法马篮,如CLS_META (0x2L),則存儲類方法;
 struct objc_cache *cache                  OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址怜奖,用于提升效率浑测;
 struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE; // 存儲該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

由以上代碼可見,類與對象的區(qū)別就是類比對象多了很多特征成員歪玲,?類也可以當(dāng)做一個(gè)objc_object來對待迁央,也就是說類和對象都是對象,分別稱作類對象(class object)和實(shí)例對象(instance object)滥崩,這樣我們就可以區(qū)別對象和類了岖圈。

isa:objc_object(實(shí)例對象)中isa指針指向的類結(jié)構(gòu)稱為class(也就是該對象所屬的類)其中存放著普通成員變量與動態(tài)方法(“-”開頭的方法);此處isa指針指向的類結(jié)構(gòu)稱為metaclass钙皮,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)幅狮。

super_class: 指向該類的父類的指針,如果該類是根類(如NSObject或NSProxy)株灸,那么super_class就為nil。

類與對象的繼承層次關(guān)系如圖(圖片源自網(wǎng)絡(luò))

所有的metaclass中isa指針都是指向根metaclass擎值,而根metaclass則指向自身慌烧。根metaclass是通過繼承根類產(chǎn)生的,與根class結(jié)構(gòu)體成員一致鸠儿,不同的是根metaclass的isa指針指向自身屹蚊。

SEL

SEL是selector在Objective-C中的表示類型。selector可以理解為區(qū)別方法的編號(ID/地址)进每。

typedef struct objc_selector *SEL;

objc_selector的定義如下:

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;// 名稱
    char *types;                      OBJC2_UNAVAILABLE;// 類型
};

name和types都是char類型汹粤。

IMP

typedef id (*IMP)(id, SEL, ...);

IMP是“implementation”的縮寫,它是由編譯器生成的一個(gè)函數(shù)指針, 指向方法(method)實(shí)現(xiàn)代碼塊的地址田晚。當(dāng)你發(fā)起一個(gè)消息后嘱兼,這個(gè)函數(shù)指針決定了最終執(zhí)行哪段代碼。

Method

Method代表類中的某個(gè)方法的類型贤徒。

typedef struct objc_method *Method;

objc_method的定義如下:

struct objc_method {
    SEL method_name                   OBJC2_UNAVAILABLE; // 方法名
    char *method_types                OBJC2_UNAVAILABLE; // 方法類型
    IMP method_imp                    OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}

方法名method_name類型為SEL芹壕,上文提到過汇四。
方法類型method_types是一個(gè)char指針,存儲著方法的參數(shù)類型和返回值類型踢涌。
方法實(shí)現(xiàn)method_imp的類型為IMP通孽,上文提到過。

Ivar

Ivar代表類中實(shí)例變量的類型

typedef struct objc_ivar *Ivar;

objc_ivar的定義如下:

struct objc_ivar {
    char *ivar_name                   OBJC2_UNAVAILABLE; // 變量名
    char *ivar_type                   OBJC2_UNAVAILABLE; // 變量類型
    int ivar_offset                   OBJC2_UNAVAILABLE; // ?基地址偏移字節(jié)
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE; // 占用空間
#endif
}

objc_property_t

objc_property_t是屬性睁壁,它的定義如下:

typedef struct objc_property *objc_property_t;

objc_property是內(nèi)置的類型背苦,與之關(guān)聯(lián)的還有一個(gè)objc_property_attribute_t,它是屬性的attribute潘明,也就是其實(shí)是對屬性的詳細(xì)描述行剂,包括屬性名稱、屬性編碼類型钉疫、原子類型/非原子類型等硼讽。它的定義如下:

typedef struct {
    const char *name; // 名稱
    const char *value;  // 值(通常是空的)
} objc_property_attribute_t;

Cache

Catch的定義如下:

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache {
    unsigned int mask                   OBJC2_UNAVAILABLE;
    unsigned int occupied               OBJC2_UNAVAILABLE;
    Method buckets[1]                   OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的總數(shù)。在方法查找中牲阁,Runtime使用這個(gè)字段確定數(shù)組的索引位置固阁。
occupied: 實(shí)際占用cache buckets的總數(shù)。
buckets: 指定Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組城菊。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素备燃。需要注意的是,指針可能是NULL凌唬,表示這個(gè)緩存bucket沒有被占用并齐,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會隨著時(shí)間而增長客税。
objc_msgSend(下文講解)每調(diào)用一次方法后况褪,就會把該方法緩存到cache列表中,下次的時(shí)候更耻,就直接優(yōu)先從cache列表中尋找测垛,如果cache沒有,才從methodLists中查找方法秧均。

Catagory

這個(gè)就是我們平時(shí)所說的類別了食侮,很熟悉吧。它可以動態(tài)的為已存在的類添加新的方法目胡。
它的定義如下:

typedef struct objc_category *Category;

objc_category的定義如下:

struct objc_category {
    char *category_name                           OBJC2_UNAVAILABLE; // 類別名稱
    char *class_name                              OBJC2_UNAVAILABLE; // 類名
    struct objc_method_list *instance_methods     OBJC2_UNAVAILABLE; // 實(shí)例方法列表
    struct objc_method_list *class_methods        OBJC2_UNAVAILABLE; // 類方法列表
    struct objc_protocol_list *protocols          OBJC2_UNAVAILABLE; // 協(xié)議列表
}

Objective-C的消息傳遞

基本消息傳遞

在面向?qū)ο缶幊讨芯馄撸瑢ο笳{(diào)用方法叫做發(fā)送消息。在編譯時(shí)誉己,程序的源代碼就會從對象發(fā)送消息轉(zhuǎn)換成Runtime的objc_msgSend函數(shù)調(diào)用眉尸。
例如某實(shí)例變量receiver實(shí)現(xiàn)某一個(gè)方法oneMethod

[receiver oneMethod];

Runtime會將其轉(zhuǎn)成類似這樣的代碼

objc_msgSend(receiver, selector);

具體會轉(zhuǎn)換成什么代碼呢?
Runtime會根據(jù)類型自動轉(zhuǎn)換成下列某一個(gè)函數(shù):
objc_msgSend: 普通的消息都會通過該函數(shù)發(fā)送
objc_msgSend_stret: 消息中有數(shù)據(jù)結(jié)構(gòu)作為返回值(不是簡單值)時(shí),通過此函數(shù)發(fā)送和接收返回值
objc_msgSendSuper: 和objc_msgSend類似效五,這里把消息發(fā)送給父類的實(shí)例
objc_msgSendSuper_stret: 和objc_msgSend_stret類似地消,這里把消息發(fā)送給父類的實(shí)例并接收返回值

消息被發(fā)送到實(shí)例對象流程
objc_msgSend函數(shù)的調(diào)用過程:
  • 第一步:檢測這個(gè)selector是不是要忽略的。
  • 第二步:檢測這個(gè)target是不是nil對象畏妖。nil對象發(fā)送任何一個(gè)消息都會被忽略掉脉执。
  • 第三步:

??1.調(diào)用實(shí)例方法時(shí),它會首先在自身isa指針指向的類(class)methodLists中查找該方法戒劫,如果找不到則會通過class的super_class指針找到父類的類對象結(jié)構(gòu)體半夷,然后從methodLists中查找該方法,如果仍然找不到迅细,則繼續(xù)通過super_class向上一級父類結(jié)構(gòu)體中查找巫橄,直至根class;

??2.當(dāng)我們調(diào)用某個(gè)某個(gè)類方法時(shí)茵典,它會首先通過自己的isa指針找到metaclass湘换,并從其中methodLists中查找該類方法,如果找不到則會通過metaclass的super_class指針找到父類的metaclass對象結(jié)構(gòu)體统阿,然后從methodLists中查找該方法彩倚,如果仍然找不到,則繼續(xù)通過super_class向上一級父類結(jié)構(gòu)體中查找扶平,直至根metaclass帆离;
第四部:前三部都找不到就會進(jìn)入動態(tài)方法解析(看下文)。

消息動態(tài)解析

動態(tài)解析流程圖(圖片來自網(wǎng)絡(luò))

請參照圖片品味以下步驟:

  • 第一步:通過resolveInstanceMethod:方法決定是否動態(tài)添加方法结澄。如果返回Yes則通過class_addMethod動態(tài)添加方法哥谷,消息得到處理,結(jié)束麻献;如果返回No们妥,則進(jìn)入下一步;
  • 第二步:這步會進(jìn)入forwardingTargetForSelector:方法勉吻,用于指定備選對象響應(yīng)這個(gè)selector监婶,不能指定為self。如果返回某個(gè)對象則會調(diào)用對象的方法餐曼,結(jié)束。如果返回nil鲜漩,則進(jìn)入第三部源譬;
  • 第三部:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil孕似,則消息無法處理踩娘。如果返回methodSignature,則進(jìn)入下一步;
  • 第四部:這步調(diào)用forwardInvocation:方法养渴,我們可以通過anInvocation對象做很多處理雷绢,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對象等理卑,如果方法調(diào)用成功翘紊,則結(jié)束。如果失敗藐唠,則進(jìn)入doesNotRecognizeSelector方法帆疟,若我們沒有實(shí)現(xiàn)這個(gè)方法,那么就會crash宇立。

Runtime實(shí)戰(zhàn)

原文中有代碼實(shí)戰(zhàn)踪宠,建議配合實(shí)戰(zhàn)代碼加深理解。

結(jié)語

這里只是對Objective-C Runtime 1小時(shí)入門教程的搬運(yùn)與筆記妈嘹,該文是我目前看過的對runtime解析最全面的一篇柳琢,建議反復(fù)解讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末润脸,一起剝皮案震驚了整個(gè)濱河市柬脸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌津函,老刑警劉巖肖粮,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尔苦,居然都是意外死亡涩馆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門允坚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魂那,“玉大人,你說我怎么就攤上這事稠项⊙难牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵展运,是天一觀的道長活逆。 經(jīng)常有香客問我,道長拗胜,這世上最難降的妖魔是什么蔗候? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮埂软,結(jié)果婚禮上锈遥,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好所灸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布丽惶。 她就那樣靜靜地躺著,像睡著了一般爬立。 火紅的嫁衣襯著肌膚如雪钾唬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天懦尝,我揣著相機(jī)與錄音知纷,去河邊找鬼。 笑死陵霉,一個(gè)胖子當(dāng)著我的面吹牛琅轧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播踊挠,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼乍桂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了效床?” 一聲冷哼從身側(cè)響起睹酌,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剩檀,沒想到半個(gè)月后憋沿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沪猴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年辐啄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片运嗜。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壶辜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出担租,到底是詐尸還是另有隱情砸民,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布奋救,位于F島的核電站岭参,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尝艘。R本人自食惡果不足惜演侯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望利耍。 院中可真熱鬧蚌本,春花似錦、人聲如沸隘梨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轴猎。三九已至嵌莉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捻脖,已是汗流浹背锐峭。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留可婶,地道東北人沿癞。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像矛渴,于是被迫代替她去往敵國和親椎扬。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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