Runtime術(shù)語

曾經(jīng)覺得Objc特別方便上手,面對(duì)著 Cocoa 中大量 API,只知道簡單的查文檔和調(diào)用。還記得初學(xué) Objective-C 時(shí)把[receiver message]當(dāng)成簡單的方法調(diào)用,而無視了“發(fā)送消息”這句話的深刻含義。其實(shí)[receiver message]會(huì)被編譯器轉(zhuǎn)化為:

objc_msgSend(receiver, selector)

如果消息含有參數(shù),則為:

objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能夠找到對(duì)應(yīng)的selector,那么就相當(dāng)于直接執(zhí)行了接收者這個(gè)對(duì)象的特定方法;否則,消息要么被轉(zhuǎn)發(fā),或是臨時(shí)向接收者動(dòng)態(tài)添加這個(gè)selector對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容,要么就干脆玩完崩潰掉。

現(xiàn)在可以看出[receiver message]真的不是一個(gè)簡簡單單的方法調(diào)用。因?yàn)檫@只是在編譯階段確定了要向接收者發(fā)送message這條消息,而receive將要如何響應(yīng)這條消息,那就要看運(yùn)行時(shí)發(fā)生的情況來決定了。

Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語言的特性,這些深層次的知識(shí)雖然平時(shí)寫代碼用的少一些,但是卻是每個(gè) Objc 程序員需要了解的。

SEL

objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的 ID,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:

typedef struct objc_selector *SEL;

其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè)SEL類型的方法選擇器。不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類型

id

objc_msgSend第一個(gè)參數(shù)類型為id,大家對(duì)它都不陌生,它是一個(gè)指向類實(shí)例的指針:

typedef struct objc_object *id;

那objc_object又是啥呢:

struct objc_object { Class isa; };

objc_object結(jié)構(gòu)體包含一個(gè)isa指針,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類。

PS:isa指針不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來確定類型,而是應(yīng)該用class方法來確定實(shí)例對(duì)象的類。因?yàn)?code>KVO的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的isa指針指向一個(gè)中間類而不是真實(shí)的類,這是一種叫做 isa-swizzling的技術(shù),詳見<a >官方文檔</a>

Class

之所以說isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:

typedef struct objc_class *Class;```
而`objc_class`就是我們摸到的那個(gè)瓜,里面的東西多著呢:
```objectivec
struct objc_class { 
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__ Class super_class                OBJC2_UNAVAILABLE; 
const char *name                                OBJC2_UNAVAILABLE; 
long version                                    OBJC2_UNAVAILABLE; 
long info                                       OBJC2_UNAVAILABLE; 
long instance_size                              OBJC2_UNAVAILABLE; 
struct objc_ivar_list *ivars                    OBJC2_UNAVAILABLE; 
struct objc_method_list **methodLists           OBJC2_UNAVAILABLE; 
struct objc_cache *cache                        OBJC2_UNAVAILABLE; 
struct objc_protocol_list *protocols            OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;

可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協(xié)議。

PS:OBJC2_UNAVAILABLE之類的宏定義是蘋果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的黑魔法,為的是兼容非Objective-C 2.0的遺留邏輯,但我們?nèi)阅軓闹蝎@得一些有價(jià)值的信息,有興趣的可以查看源代碼。

Objective-C 2.0 的頭文件雖然沒暴露出objc_class結(jié)構(gòu)體更詳細(xì)的設(shè)計(jì),我們依然可以從Objective-C 1.0 的定義中小窺端倪:

objc_class結(jié)構(gòu)體中:ivarsobjc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說可以動(dòng)態(tài)修改*methodLists的值來添加成員方法,這也是Category實(shí)現(xiàn)的原理,同樣解釋了Category不能添加屬性的原因。而最新版的 Runtime 源碼對(duì)這一塊的<a >描述</a>已經(jīng)有很大變化,可以參考下美團(tuán)技術(shù)團(tuán)隊(duì)的深入理解<a >Objective-C:Category</a>。

PS:任性的話可以在Category中添加@dynamic的屬性,并利用運(yùn)行期動(dòng)態(tài)提供存取方法或干脆動(dòng)態(tài)轉(zhuǎn)發(fā);或者干脆使用關(guān)聯(lián)度對(duì)象(AssociatedObject)

其中objc_ivar_listobjc_method_list分別是成員變量列表和方法列表:

struct objc_ivar_list { 
          int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__ 
          int space OBJC2_UNAVAILABLE;
#endif
       /* variable length structure */ 
       struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
       struct objc_method_list { 
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
       int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
       int space OBJC2_UNAVAILABLE;
#endif 
      /* variable length structure */ 
      struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

如果你C語言不是特別好,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類的單個(gè)成員變量的信息;同理objc_method_list結(jié)構(gòu)體存儲(chǔ)著objc_method數(shù)組列表,而objc_method結(jié)構(gòu)體存儲(chǔ)了類的某個(gè)方法的信息。

最后要提到的還有一個(gè)objc_cache,顧名思義它是緩存,它在objc_class的作用很重要,在后面會(huì)講到。

不知道你是否注意到了objc_class中也有一個(gè)isa對(duì)象,這是因?yàn)橐粋€(gè) ObjC 類本身同時(shí)也是一個(gè)對(duì)象,為了處理類和對(duì)象的關(guān)系,runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西,類對(duì)象所屬類型就叫做元類,它用來表述類對(duì)象本身所具備的元數(shù)據(jù)。類方法就定義于此處,因?yàn)檫@些方法可以理解成類對(duì)象的實(shí)例方法。每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類。當(dāng)你發(fā)出一個(gè)類似[NSObject alloc]的消息時(shí),你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類對(duì)象 (Class Object) ,這個(gè)類對(duì)象必須是一個(gè)元類的實(shí)例,而這個(gè)元類同時(shí)也是一個(gè)根元類 (root meta class) 的實(shí)例。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類對(duì)象的時(shí)候,objc_msgSend()會(huì)去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了,然后對(duì)這個(gè)類對(duì)象執(zhí)行方法調(diào)用。

bdc7f74c-f1d1-4b63-8a35-b2a5cf741f9a.jpg

上圖實(shí)線是super_class 指針,虛線是isa指針。 有趣的是根元類的超類是NSObject,而isa指向了自己,而NSObject的超類為nil,也就是它沒有超類。

Method

Method是一種代表類中的某個(gè)方法的類型。

typedef struct objc_method *Method;

objc_method在上面的方法列表中提到過,它存儲(chǔ)了方法名,方法類型和方法實(shí)現(xiàn):

struct objc_method { 
SEL method_name                              OBJC2_UNAVAILABLE; 
char *method_types                           OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
  • 方法名類型為SEL,前面提到過相同名字的方法即使在不同類中定義,它們的方法選擇器也相同。
  • 方法類型method_types是個(gè)char指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型。
  • method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)指針,后面會(huì)詳細(xì)講到。

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;
#ifdef __LP64__ 
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

可以根據(jù)實(shí)例查找其在類中的名字,也就是“反射”:

-(NSString *)nameWithInstance:(id)instance { 
unsigned int numIvars = 0; 
NSString *key=nil;
 Ivar * ivars = class_copyIvarList([self class], &numIvars); 
for(int i = 0; i < numIvars; i++) { 
Ivar thisIvar = ivars[i];
 const char *type = ivar_getTypeEncoding(thisIvar);
 NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
 if (![stringType hasPrefix:@"@"]) { 
continue; 
} 
if ((object_getIvar(self, thisIvar) == instance)) {//此處若 crash 不要慌! 
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
 break;
   } 
} 
free(ivars); 
return key;
}

class_copyIvarList 函數(shù)獲取的不僅有實(shí)例變量,還有屬性。但會(huì)在原本的屬性名前加上一個(gè)下劃線。

IMP

IMP在objc.h中的定義是:

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

它就是一個(gè)<a >函數(shù)指針</a>,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC消息之后,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開消息傳遞階段,直接執(zhí)行方法。

你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類型相同,參數(shù)都包含idSEL類型。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過一組idSEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址;反之亦然。

Cache

runtime.hCache的定義如下:

typedef struct objc_cache *Cache

還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache *cache吧,它到底是緩存啥的呢,先看看objc_cache的實(shí)現(xiàn):

struct objc_cache { 
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
 unsigned int occupied OBJC2_UNAVAILABLE; 
Method buckets[1] OBJC2_UNAVAILABLE;
};

Cache為方法調(diào)用的性能進(jìn)行優(yōu)化,通俗地講,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí),它不會(huì)直接在isa指向的類的方法列表中遍歷查找能夠響應(yīng)消息的方法,因?yàn)檫@樣效率太低了,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過的 CPU繞過主存先訪問Cache的道理挺像,而我猜蘋果為提高Cache命中率應(yīng)該也做了努力吧。

Property

@property標(biāo)記了類中的屬性,這個(gè)不必多說大家都很熟悉,它是一個(gè)指向objc_property結(jié)構(gòu)體的指針:

typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個(gè)更常用

可以通過class_copyPropertyListprotocol_copyPropertyList方法來獲取類和協(xié)議中的屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回類型為指向指針的指針,哈哈,因?yàn)閷傩粤斜硎莻€(gè)數(shù)組,每個(gè)元素內(nèi)容都是一個(gè)objc_property_t指針,而這兩個(gè)函數(shù)返回的值是指向這個(gè)數(shù)組的指針。舉個(gè)例子,先聲明一個(gè)類:

interface Lender : NSObject { 
float alone;
}
@property float alone;
@end

你可以用下面的代碼獲取屬性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

你可以用property_getName函數(shù)來查找屬性名稱:

const char *property_getName(objc_property_t property)

你可以用class_getPropertyprotocol_getProperty通過給出的名稱來在類和協(xié)議中獲取屬性的引用:

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

你可以用property_getAttributes函數(shù)來發(fā)掘?qū)傩缘拿Q和@encode類型字符串:

const char *property_getAttributes(objc_property_t property)

把上面的代碼放一起,你就能從一個(gè)類中獲取它的屬性啦:

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) { 
objc_property_t property = properties[i]; 
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}

對(duì)比下class_copyIvarList 函數(shù),使用 class_copyPropertyList 函數(shù)只能獲取類的屬性,而不包含成員變量。但此時(shí)獲取的屬性名是不帶下劃線的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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