八别凤、Runtime 概念部分 之 message.md

本位github地址 https://github.com/ICZhuang/Runtime

我們通常定義一個類的時候揩页,會直接將方法寫到類中,給予一種調(diào)用改對象方法就一定會找到該對象的方法實現(xiàn)的錯覺砚作。其實不然。我們說調(diào)用一個對象的某一個對象方法嘹锁,其實更準(zhǔn)確的描述應(yīng)該是向?qū)ο蟀l(fā)送一個消息葫录,比如[receiver message],應(yīng)該描述成像receiver這個對象發(fā)送一個message消息领猾。


objc_msgSend

在Objective-C語言中米同,消息并沒有跟方法的實現(xiàn)綁定的骇扇,直到運(yùn)行狀態(tài)下,才知道響應(yīng)消息時應(yīng)該執(zhí)行的方法面粮。比如編譯器會將下面表達(dá)式

[receiver message]

轉(zhuǎn)換成消息發(fā)送方法 objc_msgSend 方式少孝。這個方法將消息接受者和方法selector作為兩個最重要的參數(shù)

objc_msgSend(receiver, selector)

任何消息傳遞過來的參數(shù)都會以可變形參的形式拼接在selector后面

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

在消息發(fā)送方法中,就是在做動態(tài)綁定

  1. 第一步查找selector的方法實現(xiàn)熬苍,相同的selector在不同的類有不同的實現(xiàn)稍走,但它會在 receiver的class中找;
  2. 然后執(zhí)行程序柴底,將receiver和其它參數(shù)作為實參傳遞過去婿脸;
  3. 最后將程序的返回值作為自己的返回值返回

查找的key就存在類的結(jié)構(gòu)中。類結(jié)構(gòu)中有兩個重要的元素:

  • superclass 指針
  • 分發(fā)表(dispatch table)似枕,這個分發(fā)表將selector和方法的實現(xiàn)地址關(guān)聯(lián)了起來

當(dāng)一個對象被創(chuàng)建盖淡,分配內(nèi)存年柠,并且給成員變量做初始化凿歼。第一個變量就是isa指針,指向它的class冗恨。通過class便可找到所有在繼承鏈中的類答憔。

image_01
image_01

當(dāng)給一個對象發(fā)送消息,消息發(fā)送方法就會沿著對象的isa指針在類的分發(fā)表里找方法的selector掀抹,如果在類中沒有找到虐拓,那么繼續(xù)在superclass的分發(fā)表里找,直到查找到NSObject class傲武。一旦查找到蓉驹,消息發(fā)送方法就調(diào)起該方法實現(xiàn)并把對象的數(shù)據(jù)結(jié)構(gòu)傳遞過去。

為了提高查找速度揪利,Runtime 會緩存那些使用過的方法态兴。每個class都有一個cache,它可以緩存繼承而來的方法和自身定義的方法疟位。在從分發(fā)表里查找之前瞻润,消息查找機(jī)制會先從cache里查找。這個緩存會動態(tài)的增長以存放新的消息指定執(zhí)行過的方法甜刻。

隱式參數(shù)

當(dāng)objc_msgSend找到了方法的實現(xiàn)绍撞,它就會調(diào)起實現(xiàn),并把消息傳遞的參數(shù)全部傳遞過去得院,其中包括兩個默認(rèn)就會傳遞的參數(shù)

  • 消息接收對象
  • method對應(yīng)的selector

通常在寫Objective-C代碼的時候傻铣,通常會省去這兩個參數(shù),當(dāng)被編譯的時候祥绞,參會插入到方法實現(xiàn)中非洲。雖然在編碼時不顯示聲明阱驾,但我們?nèi)匀豢梢詰?yīng)用到它們。?self 則為消息接收對象怪蔑, _cmd 則為消息的 selector里覆。比如下面示例,_cmd 是strange的selector缆瓣,self 則是接收strange消息的接受者

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

方法地址

唯一可以避開動態(tài)綁定的方法就是獲得方法(實現(xiàn))的地址喧枷,直接調(diào)用。一個方法被多次連續(xù)調(diào)用弓坞,而又想避開每次都要查找的消耗隧甚,可以用這種方法避開動態(tài)綁定。

通過NSObject class中聲明的 methodForSelector: 可以獲得方法的地址(IMP)渡冻,就是一個函數(shù)指針戚扳。通過函數(shù)指針喚起它的實現(xiàn)。但是得注意函數(shù)指針與IMP的轉(zhuǎn)換族吻,返回值和參數(shù)都需要包含到

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);

前面兩個參數(shù)是用來接收 self 和 _cmd 的帽借,轉(zhuǎn)成函數(shù)形式需要加上。

注意 methodForSelector: 是 Cocoa runtime 提供的超歌,并非Objective-C語言提供的砍艾。


給一個對象發(fā)送消息后,如果對應(yīng)的方法沒找到巍举,則進(jìn)入下面步驟:

  1. 動態(tài)方法解析:喚起對象的resolveInstanceMethod: 或者 resolveClassMethod: 脆荷,詢問是否能夠處理消息的selector,如果不能則進(jìn)入下一步懊悯;
  2. 備援對象:喚起對象的forwardingTargetForSelector:蜓谋,返回能夠處理該selector的對象,如果返回nil或者self炭分,則進(jìn)入下一步桃焕;
  3. 消息轉(zhuǎn)發(fā):實現(xiàn)forwardInvocation:將消息轉(zhuǎn)發(fā)給其它能處理該消息的對象,該方法的參數(shù)會通過methodSignatureForSelector:創(chuàng)建欠窒,所以還應(yīng)該實現(xiàn)methodSignatureForSelector:方法

參考:理解消息傳遞機(jī)制 [Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理]


動態(tài)方法解析

有些情況你可能希望動態(tài)的提供方法實現(xiàn)覆旭。比如聲明了一個動態(tài)屬性

@dynamic propertyName;

這說明與改屬性相關(guān)的方法(setter&getter)要動態(tài)添加

可以實現(xiàn) resolveInstanceMethod: 和 resolveClassMethod: 動態(tài)的為對象或者類的selector添加實現(xiàn)。

一個Objective-C的方法其實在底層就是一個簡單的C函數(shù)岖妄,這函數(shù)帶著至少兩個參數(shù)型将,self 和 _cmd。通過class_addMethod添加一個函數(shù)作為類的方法荐虐。因此七兜,先添加一個函數(shù)

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

然后在 resolveInstanceMethod: 中動態(tài)的將它作為方法(如resolveThisMethodDynamically)添加到class中

@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

消息轉(zhuǎn)發(fā)和動態(tài)方法解析是順序的,在進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制之前福扬,可以通過動態(tài)方法解析來處理方法腕铸。如果respondsToSelector: 和 instancesRespondToSelector: 被喚起惜犀,通過動態(tài)方法處理為selector提供一個IMP。如果實現(xiàn)了resolveInstanceMethod: 但又想讓selector進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制狠裹,那么在遇到該selector時返回NO虽界。

經(jīng)試驗,如果實現(xiàn)了resolveInstanceMethod: 并且在里面往類添加了selector關(guān)聯(lián)的方法實現(xiàn)涛菠,則不會再走備援對象和消息轉(zhuǎn)發(fā)機(jī)制莉御。


消息轉(zhuǎn)發(fā)

在出現(xiàn)崩潰之前,runtime 給對象發(fā)送 forwardInvocation: 消息俗冻,并將一個NSInvocation對象作為唯一的參數(shù)傳遞過去礁叔,這個NSInvocation對象囊括了最初的消息和參數(shù)。

你可以實現(xiàn) forwardInvocation: 方法提供對消息的默認(rèn)處理迄薄,或者過濾消息避免出現(xiàn)錯誤琅关。顧名思義,方法forwardInvocation:通常是用來將轉(zhuǎn)發(fā)消息給另外的對象讥蔽。

為了了解消息轉(zhuǎn)發(fā)的意圖涣易,想象這樣一種場景:假設(shè)需要設(shè)計一個對象可以響應(yīng)negotiate消息,并且希望它的響應(yīng)期間可以帶動另外一種對象響應(yīng)negotiate消息勤篮。完成這需求很簡單都毒,可以在你的negotiate方法實現(xiàn)中將消息傳遞給另外一個對象色罚。

進(jìn)一步假設(shè)碰缔,你設(shè)計的對象對negotiate的響應(yīng)實際上其實就是執(zhí)行另外一個類的實現(xiàn),其中一種方法就是讓你的類繼承自該類戳护。然而金抡,這可能并不合理。原因在于腌且,你的類和實現(xiàn)了negotiate的類可能在不同的繼承樹分支梗肝。就是說它們可能不存在直接或間接的繼承關(guān)系。

即使你不能繼承negotiate方法铺董,你可以簡單地實現(xiàn)negotiate巫击,將negotiate消息傳遞給另外一個對象。如此一來你的對象就響應(yīng)了negotiate精续,就像是從其它對象"借用"到了negotiate坝锰。

- (id)negotiate {
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}

這種方法有些笨拙,特別是當(dāng)有多種消息需要傳遞給其它對象的情況重付。你需要在一個方法中覆蓋所有其它對象應(yīng)該響應(yīng)的方法顷级。此外,在寫代碼的時候确垫,有一些消息的轉(zhuǎn)發(fā)是不可預(yù)見的弓颈。這些消息是取決于runtime帽芽,它們可能被新方法替代,或被新類重新實現(xiàn)翔冀。

forwardInvocation: 提供的是一種動態(tài)解決方案导街。它工作的原理就想這樣:當(dāng)一個對象沒有一個方法可以匹配消息中的selector,這個對象是不能響應(yīng)該消息的纤子,這個時候菊匿,runtime 會給對象發(fā)送一個 forwardInvocation: 消息。所有的對象都從NSObject類中繼承了改方法计福。然而跌捆,NSObject中的該方法只是簡單的調(diào)起doesNotRecognizeSelector: 。通過重寫 forwardInvocation: 方法象颖,將消息轉(zhuǎn)發(fā)給另外的對象佩厚。

轉(zhuǎn)發(fā)一個消息,需要在forwardInvocation:方法中:

  • 判斷消息應(yīng)該轉(zhuǎn)發(fā)給誰说订,還有
  • 轉(zhuǎn)發(fā)的時候帶上它原始的參數(shù)

消息可以通過 invokeWithTarget: 方法發(fā)送:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([someOtherObject respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

如果有返回值抄瓦,則返回值會返給最初的發(fā)送者,包括任何類型 ids, structures, and double-precision floating-point numbers.

forwardInvocation: 方法就像是未知消息的分發(fā)中心陶冷,將它們投遞到指定的接受者钙姊。由或者是中轉(zhuǎn)站,將消息發(fā)送給同一目標(biāo)埂伦。它可以將一個消息轉(zhuǎn)換成另一個消息煞额,或者簡單的就把消息"吞下",讓消息得不到響應(yīng)沾谜。forwardInvocation: 所干的事兒就看它是怎么被實現(xiàn)的膊毁。

注意:forwardInvocation: 只有在消息的接受者不能夠響應(yīng)的情況下才會去處理該消息。舉例來說基跑,如果希望一個對象轉(zhuǎn)發(fā)negotiate消息給其它的對象婚温,那么這個對象不能有negotiate方法,如果有媳否,消息永遠(yuǎn)也不可能被forwardInvocation:處理栅螟。

轉(zhuǎn)發(fā)和多繼承

如下圖,一個Warrior實例對象轉(zhuǎn)發(fā)一個negotiate消息給一個Diplomat實例對象篱竭。Warrior就好像Diplomat一樣能響應(yīng)negotiate消息

image-02
image-02

這樣看來力图,轉(zhuǎn)發(fā)消息的對象就"繼承"了來自兩個繼承樹分支中的方法 — 本類所在繼承分支和消息真正響應(yīng)的對象所在的分支。按白話說室抽,轉(zhuǎn)發(fā)消息的對象就想繼承了多個對象搪哪,2?而這些對象不存在直接或間接的繼承關(guān)系。

消息轉(zhuǎn)發(fā)提供了多繼承中的許多特性。但是晓折,他們兩這有一點不同的是:多繼承是將多個不同的行為合并到一個對象里面惑朦。它趨向更大,更豐富的對象的定義漓概。相反漾月,轉(zhuǎn)發(fā)趨向?qū)⑷蝿?wù)分割派給不同的對象。它將問題分解成不同的對象胃珍,又將這些對象以一種方式關(guān)聯(lián)起來梁肿,但這種方式對消息發(fā)送者來說是透明的。

轉(zhuǎn)發(fā)和繼承

雖說轉(zhuǎn)發(fā)很像繼承觅彰,但NSObject class卻能清楚的區(qū)分它們吩蔑。像respondsToSelector:和 isKindOfClass: 這樣的方法只會是從繼承樹種查找,并不會在轉(zhuǎn)發(fā)鏈中查找填抬。舉例來說烛芬,一個Warrior object想知道自己是否響應(yīng)negotiate消息

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

答案是NO,即使它能接收negotiate消息飒责,某種意義上赘娄,它只是轉(zhuǎn)發(fā)消息給Diplomat object。

除非宏蛉,有這樣一種情況遣臼,你希望它表現(xiàn)出來的是仿佛繼承了消息轉(zhuǎn)發(fā)的目的對象一樣,你需要重新實現(xiàn)respondsToSelector: 和 isKindOfClass:

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
      /// 這里拾并,檢測 aSelector 消息是否可以被轉(zhuǎn)發(fā)到另一個對象揍堰,或者另一個對象是否
      /// 是否可以響應(yīng) aSelector 消息, 如果可以retrun YES
    }
    return NO;
}

如果一個對象可以轉(zhuǎn)發(fā)消息給它的代理對象辟灰,你需要像下面一樣實現(xiàn)methodSignatureForSelector:方法

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末个榕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芥喇,更是在濱河造成了極大的恐慌,老刑警劉巖凰萨,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件继控,死亡現(xiàn)場離奇詭異,居然都是意外死亡胖眷,警方通過查閱死者的電腦和手機(jī)武通,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來珊搀,“玉大人冶忱,你說我怎么就攤上這事【澄觯” “怎么了枢希?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵速那,是天一觀的道長吝羞。 經(jīng)常有香客問我,道長默赂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任括勺,我火速辦了婚禮缆八,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疾捍。我一直安慰自己奈辰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布乱豆。 她就那樣靜靜地躺著冯挎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咙鞍。 梳的紋絲不亂的頭發(fā)上房官,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音续滋,去河邊找鬼翰守。 笑死,一個胖子當(dāng)著我的面吹牛疲酌,可吹牛的內(nèi)容都是我干的蜡峰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朗恳,長吁一口氣:“原來是場噩夢啊……” “哼湿颅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粥诫,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤油航,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怀浆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谊囚,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年执赡,在試婚紗的時候發(fā)現(xiàn)自己被綠了镰踏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沙合,死狀恐怖奠伪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤绊率,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布谨敛,位于F島的核電站,受9級特大地震影響即舌,放射性物質(zhì)發(fā)生泄漏佣盒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一顽聂、第九天 我趴在偏房一處隱蔽的房頂上張望肥惭。 院中可真熱鬧,春花似錦紊搪、人聲如沸蜜葱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牵囤。三九已至,卻和暖如春滞伟,著一層夾襖步出監(jiān)牢的瞬間揭鳞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工梆奈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留野崇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓亩钟,卻偏偏與公主長得像乓梨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子清酥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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

  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,135評論 0 9
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉扶镀,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • 前言 Runtime是iOS開發(fā)者進(jìn)階必須學(xué)習(xí)的一個知識點。網(wǎng)上關(guān)于Runtime 有許多介紹焰轻,有深入有簡單介紹臭觉,...
    雨田_Toping閱讀 1,577評論 1 1
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 732評論 0 2
  • 前言 Runtime是iOS開發(fā)者進(jìn)階必須學(xué)習(xí)的一個知識點。網(wǎng)上關(guān)于Runtime 有許多介紹鹦马,有深入有簡單介紹胧谈,...
    獨酌丿紅顏閱讀 302評論 0 6