iOS必備技能之Runtime(一)

Runtime 是一個(gè)比較底層的C語(yǔ)言的API傀顾,可以翻譯為“運(yùn)行時(shí)”靡羡。作為使用運(yùn)行時(shí)機(jī)制的OC語(yǔ)言的底層犀盟,它在程序運(yùn)行時(shí)把OC語(yǔ)言轉(zhuǎn)換成了runtime的C語(yǔ)言代碼拣度。學(xué)習(xí)并理解runtime是OC學(xué)習(xí)歷程中的不可或缺的一大塊兒碎绎。

一、消息機(jī)制

調(diào)用方法的本質(zhì)就是發(fā)送消息抗果。

發(fā)送消息常見(jiàn)的有四個(gè)方法:

  • objc_msgSend 向一個(gè)類(lèi)的實(shí)例發(fā)送消息筋帖,返回id類(lèi)型數(shù)據(jù)。(這也是最常用的一個(gè)發(fā)送消息的方法)
  • objc_msgSend_stret 向一個(gè)類(lèi)的實(shí)例發(fā)送消息冤馏,返回結(jié)構(gòu)體類(lèi)型數(shù)據(jù)日麸。
  • objc_msgSendSuper 向一個(gè)類(lèi)的實(shí)例的父類(lèi)發(fā)送消息,返回id類(lèi)型數(shù)據(jù)逮光。
  • objc_msgSendSuper_stret 向一個(gè)類(lèi)的實(shí)例的父類(lèi)發(fā)送消息代箭,返回結(jié)構(gòu)體類(lèi)型的數(shù)據(jù)。

在OC語(yǔ)言中涕刚,方法的真正實(shí)現(xiàn)是在程序運(yùn)行的時(shí)候綁定的嗡综,假如一個(gè)方法只有聲明,沒(méi)有實(shí)現(xiàn)杜漠,調(diào)用后在編譯階段是不會(huì)出錯(cuò)的极景,真正報(bào)錯(cuò)是在運(yùn)行的時(shí)候。

[receiver message]

以上方法在運(yùn)行時(shí)會(huì)被轉(zhuǎn)化為

//receiver是方法的調(diào)用者碑幅,selector是方法名
objc_msgSend(receiver, selector)
//如果有參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)

發(fā)送消息的原理

objc_msgSend為了完成動(dòng)態(tài)綁定戴陡,進(jìn)行了以下三步:

  1. 首先它要先根據(jù)方法名找到方法的具體實(shí)現(xiàn)程序,因?yàn)槎鄳B(tài)性沟涨,同一個(gè)方法在不同的類(lèi)里面可以有不同的實(shí)現(xiàn)恤批,所以查找主要依靠尋找receiver所在的類(lèi)。
  2. 傳遞參數(shù)裹赴,調(diào)用該方法的實(shí)現(xiàn)程序喜庞。
  3. 把該程序的返回值作為方法自己的返回值诀浪。
//runtime中對(duì)類(lèi)的定義
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;

//runtime中對(duì)實(shí)例的定義
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

如上runtime中對(duì)類(lèi)的定義,每一個(gè)類(lèi)都有指向父類(lèi)的指針(super_class)和一個(gè)方法調(diào)度表(objc_method_list **methodLists:根據(jù)方法名SEL查找該方法的具體實(shí)現(xiàn)的地址IMP)延都,當(dāng)向一個(gè)對(duì)象發(fā)送消息的時(shí)候雷猪,該對(duì)象通過(guò)isa指針找到該對(duì)象的類(lèi)(實(shí)際上,實(shí)例的定義里面也只有這個(gè)指針晰房,沒(méi)有別的了)求摇,在類(lèi)的調(diào)度表查找該方法名,當(dāng)找不到的時(shí)候殊者,通過(guò)指向父類(lèi)的指針找到該類(lèi)的父類(lèi)与境,然后在該類(lèi)的父類(lèi)中繼續(xù)查找該方法名,這樣遞歸查找一直到NSObject類(lèi)為止(NSProxy類(lèi)除外猖吴,它不屬于NSObject子類(lèi))摔刁。如果查找到該方法名,根據(jù)調(diào)度表找到該方法的實(shí)現(xiàn)的地址進(jìn)行調(diào)用海蔽。如下圖所示

Messaging Framework

為了加速發(fā)送消息的進(jìn)程共屈,runtime系統(tǒng)會(huì)把使用過(guò)的方法名和對(duì)應(yīng)的內(nèi)存地址緩存起來(lái),每個(gè)類(lèi)都有一個(gè)單獨(dú)的緩存空間党窜,其中包含自己類(lèi)的方法和繼承自父類(lèi)的方法拗引。在查找調(diào)度表之前,runtime系統(tǒng)會(huì)首先在緩存中進(jìn)行查找幌衣。

使用隱藏的參數(shù)

當(dāng)objc_msgSend找到方法的實(shí)現(xiàn)程序時(shí)寺擂,它調(diào)用這個(gè)程序并傳遞所有方法的參數(shù)給它,這其中還包含兩個(gè)隱藏的參數(shù):

  • 消息的接收對(duì)象
  • 調(diào)用方法的方法名(selector)

這兩個(gè)參數(shù)雖然沒(méi)有在方法中進(jìn)行定義泼掠,但是你可以很方便地使用它們。消息的接收對(duì)象通過(guò)self來(lái)引用垦细,方法名通過(guò)_cmd來(lái)引用择镇。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

獲取方法的地址

避免動(dòng)態(tài)綁定的唯一方法就是直接獲得方法的地址然后把它當(dāng)做函數(shù)一樣來(lái)調(diào)用。當(dāng)一個(gè)方法被連續(xù)多次執(zhí)行括改,而你又不想每次都用消息機(jī)制造成額外的開(kāi)支腻豌,這種辦法就是一個(gè)合適的使用時(shí)機(jī)。
下面的例子展示了如何節(jié)省開(kāi)支多次調(diào)用setFilled:方法

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

通過(guò)methodForSelector:方法嘱能,你可以請(qǐng)求得到指向?qū)崿F(xiàn)該方法的程序的指針吝梅,然后通過(guò)這個(gè)指針調(diào)用該程序。值的注意的是惹骂,參數(shù)和返回值要正確聲明苏携,而且參數(shù)中id和SEL要進(jìn)行顯式聲明。


二对粪、動(dòng)態(tài)方法

假如你想動(dòng)態(tài)地為方法提供實(shí)現(xiàn)右冻,OC使用@dynamic實(shí)現(xiàn)了這個(gè)特性装蓬。

@dynamic propertyName;

這樣就會(huì)通知編譯器和這個(gè)屬性相關(guān)的方法將會(huì)動(dòng)態(tài)提供。你可以通過(guò)方法resolveInstanceMethod:resolveClassMethod:分別為類(lèi)方法和實(shí)例方法動(dòng)態(tài)地提供實(shí)現(xiàn)纱扭。

一個(gè)OC的方法其實(shí)就是由C語(yǔ)言的函數(shù)再加上至少兩個(gè)參數(shù)(self和_cmd)組成的牍帚。

你可以把一個(gè)函數(shù)通過(guò)class_addMethod作為方法添加到一個(gè)類(lèi)中去。給定以下一個(gè)函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

你可以通過(guò)resolveInstanceMethod:這個(gè)方法把上面的函數(shù)以方法名(resolveThisMethodDynamically)動(dòng)態(tài)地添加到一個(gè)類(lèi)(MyClass)里面乳蛾。具體實(shí)現(xiàn)方式如下:

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

這其中暗赶,class_addMethod這個(gè)方法有四個(gè)參數(shù),第一個(gè)是要添加方法的類(lèi)肃叶,第二個(gè)是要添加的方法名蹂随,第三個(gè)是這個(gè)方法的實(shí)現(xiàn)函數(shù)的指針(值的注意的是,這個(gè)函數(shù)必須顯式地把self_cmd這兩個(gè)參數(shù)寫(xiě)出來(lái))被环,第四個(gè)是方法的參數(shù)數(shù)組糙及,在這里它是用的類(lèi)型編碼的方式進(jìn)行表示的,因?yàn)榉椒ㄒ欢ê?code>self和_cmd這兩個(gè)參數(shù)筛欢,所以字符數(shù)組的第二個(gè)和第三個(gè)字符一定是"@:",第一個(gè)字符代表返回值浸锨,這里為空用“v”來(lái)表示。相關(guān)知識(shí)點(diǎn)請(qǐng)見(jiàn)下文版姑。


三柱搜、類(lèi)型編碼

為了使runtime系統(tǒng)更加簡(jiǎn)潔,編譯器把每個(gè)方法的返回值和參數(shù)的類(lèi)型都分別使用一個(gè)字符來(lái)編碼剥险,然后再把它們關(guān)聯(lián)到方法選擇器(selector)上聪蘸。因?yàn)檫@種編碼方案在其它環(huán)境中也很實(shí)用,所以我們可以很方便地使用@encode()編譯器指令來(lái)自定義類(lèi)似的編碼表制。

char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);

一般來(lái)說(shuō)健爬,不管是基本類(lèi)型,還是指針么介,或者結(jié)構(gòu)體娜遵,或者聯(lián)合體,甚至可以是類(lèi)名壤短,只要這個(gè)類(lèi)型能夠作為C語(yǔ)言中sizeof()的參數(shù)设拟,那么它就能被進(jìn)行編碼。

下表便是已經(jīng)定義了的類(lèi)型編碼久脯,使用@encode()編譯器指令自定義編碼的時(shí)候一定要避開(kāi)這些字符纳胧。


Objective-C type encodings

特別注意,OC不支持long double類(lèi)型帘撰,因此@encode(long double)會(huì)返回字符“d"跑慕,意義為double
結(jié)構(gòu)體的類(lèi)型編碼是按照結(jié)構(gòu)體內(nèi)部的類(lèi)型的順序來(lái)表示的摧找,比如

typedef struct example {
    id   anObject;
    char *aString;
    int  anInt;
} Example;

會(huì)被編碼為:

{example=@*i}

由第一章內(nèi)容可以得知相赁,類(lèi)的實(shí)例的定義是一個(gè)只包含isa指針的結(jié)構(gòu)體相寇,所以[NSObject class]會(huì)被編碼為

{ NSObject=# }

具體應(yīng)用方面,上一章class_addMethod最后一個(gè)參數(shù)就是使用的類(lèi)型編碼來(lái)表示的函數(shù)返回值和參數(shù)的類(lèi)型钮科。


參考:《Objective-C Runtime Programing Guide》


鏈接:
iOS必備技能之Runtime(二)

文章會(huì)不定期進(jìn)行增添和更新唤衫,歡迎訂閱和收藏!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绵脯,一起剝皮案震驚了整個(gè)濱河市佳励,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛆挫,老刑警劉巖赃承,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悴侵,居然都是意外死亡瞧剖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)可免,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抓于,“玉大人,你說(shuō)我怎么就攤上這事浇借∽酱椋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵妇垢,是天一觀(guān)的道長(zhǎng)巾遭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)闯估,這世上最難降的妖魔是什么灼舍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮涨薪,結(jié)果婚禮上片仿,老公的妹妹穿的比我還像新娘。我一直安慰自己尤辱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布厢岂。 她就那樣靜靜地躺著光督,像睡著了一般。 火紅的嫁衣襯著肌膚如雪塔粒。 梳的紋絲不亂的頭發(fā)上结借,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音卒茬,去河邊找鬼船老。 笑死咖熟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柳畔。 我是一名探鬼主播馍管,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼薪韩!你這毒婦竟也來(lái)了确沸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤俘陷,失蹤者是張志新(化名)和其女友劉穎罗捎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拉盾,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桨菜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捉偏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒得。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖告私,靈堂內(nèi)的尸體忽然破棺而出屎暇,到底是詐尸還是另有隱情,我是刑警寧澤驻粟,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布根悼,位于F島的核電站,受9級(jí)特大地震影響蜀撑,放射性物質(zhì)發(fā)生泄漏挤巡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一酷麦、第九天 我趴在偏房一處隱蔽的房頂上張望矿卑。 院中可真熱鬧,春花似錦沃饶、人聲如沸母廷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琴昆。三九已至,卻和暖如春馆揉,著一層夾襖步出監(jiān)牢的瞬間业舍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舷暮,地道東北人态罪。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像下面,于是被迫代替她去往敵國(guó)和親复颈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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

  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,554評(píng)論 33 466
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉诸狭,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • objc_getAssociatedObject返回與給定鍵的特定對(duì)象關(guān)聯(lián)的值券膀。ID objc_getAssoci...
    有一種再見(jiàn)叫青春閱讀 1,580評(píng)論 0 7
  • 感覺(jué)是久未進(jìn)食,只管一路行尸走肉驯遇,最后弄得自己渾身不是芹彬,極度缺乏營(yíng)養(yǎng)。
    子悅zy閱讀 226評(píng)論 0 0
  • 夜晚十點(diǎn)叉庐。 鴉兒渡口酒吧舒帮。 身著紅色薄紗的鋼管舞女在歡呼和口哨聲中賣(mài)力的扭動(dòng)著雪白的身軀,長(zhǎng)發(fā)隨著旋轉(zhuǎn)掃過(guò)五彩...
    炮哥的篝火世界閱讀 425評(píng)論 0 1