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。
所有的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í)例并接收返回值
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)解析
請參照圖片品味以下步驟:
- 第一步:通過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ù)解讀。