《Effective Objective-C 2.0 》 閱讀筆記 item12

第12條:理解消息轉(zhuǎn)發(fā)機(jī)制

1. 消息轉(zhuǎn)發(fā)機(jī)制

當(dāng)對象接收到無法解讀的消息后筒繁,就會啟動“消息轉(zhuǎn)發(fā)”機(jī)制简卧,開發(fā)者可經(jīng)由此過程告訴對象應(yīng)該如何處理未知消息。

消息轉(zhuǎn)發(fā)分為兩大階段

  • 第一階段:先征詢接收者所屬的類塞祈,看其是否能動態(tài)添加方法喊暖,以處理當(dāng)前這個(gè)“未知的選擇器”,這叫做“動態(tài)方法解析”(dynamic method resolution)劈狐。
  • 第二階段:涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”(full forwarding mechanism)罐孝。
    運(yùn)行時(shí)系統(tǒng)會請求接收者以“動態(tài)新增方法”之外的手段來處理與消息相關(guān)的方法調(diào)用,這又細(xì)分為兩小步肥缔。首先莲兢,請接收者看看有沒有其他對象能處理這條消息。若有续膳,則運(yùn)行期系統(tǒng)會把消息轉(zhuǎn)給那個(gè)對象改艇,于是消息轉(zhuǎn)發(fā)過程結(jié)束。若沒有“備援的接收者”姑宽,則啟動完整的消息轉(zhuǎn)發(fā)機(jī)制遣耍,運(yùn)行時(shí)系統(tǒng)會把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中,再給接收者最后一次機(jī)會炮车,令其設(shè)法解決當(dāng)前還沒處理的這條消息。

2. 動態(tài)方法解析

對象在收到無法解讀的消息后酣溃,首先將調(diào)用其所屬類的下列類方法:

+ (BOOL)resolveInstanceMethod:(SEL)selector

解釋:selector是未知的選擇器瘦穆,返回值為Boolean類型,表示這個(gè)類是否能新增一個(gè)實(shí)例方法用以處理此選擇器赊豌。

在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)之前扛或,本類有機(jī)會新增一個(gè)處理未知選擇器的方法,便是通過調(diào)用“resolveInstanceMethod:”或“resolveClassMethod:”方法來實(shí)現(xiàn)的碘饼。
但是熙兔,使用這種辦法有個(gè)前提:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好,只等著運(yùn)行的時(shí)候動態(tài)插在類里面就可以了艾恼。此方案常用來實(shí)現(xiàn)@dynamic屬性住涉。

id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);

+ (BOOL)resolveInstanceMethod:(SEL)selector{
    NSString *selectorString = NSStringFromSelector(selector);// 將選擇器轉(zhuǎn)換為字符串
    if(/* selector is from a @dynamic property */){// 使用了@dynamic屬性
        if([selectorString hasPrefix:@"set"]){
            class_addMethod(self,
                            selector,
                            (IMP)autoDictionarySetter,
                            "V@:@");
        }else{
            class_addMethod(self,
                            selector,
                            (IMP)autoDictionaryGetter,
                            "@@:");
        }
        return YES;
    }
    return [super resolveInstanceMethod:selector];
}

3. 備援接收者

在第二階段的第一小步中,運(yùn)行期系統(tǒng)會問未知的選擇器能不能把這條消息轉(zhuǎn)發(fā)給其他接收者來處理钠绍。與該步驟對應(yīng)的處理方法如下:

- (id)forwardingTargetForSelector:(SEL)selector

解釋:selector代表未知的選擇器舆声,若當(dāng)前接收者能找到備援對象,則將其返回,若找不到媳握,就返回nil碱屁。
通過此方案,可以用“組合”(composition)來模擬出“多重繼承”(multiple inheritance)的某些特性蛾找。
注意:開發(fā)者無法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息娩脾。若是想在發(fā)送給備援接收者之前先修改消息內(nèi)容,那就得通過完整的消息轉(zhuǎn)發(fā)機(jī)制來做了打毛。

4. 完整的消息轉(zhuǎn)發(fā)

若沒有“備援的接收者”柿赊,則啟動完整的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行時(shí)系統(tǒng)會把與尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中隘冲。在觸發(fā)NSInvocation對象時(shí)闹瞧,“消息派發(fā)系統(tǒng)”(message-dispatch system)將親自出馬,把消息指派給目標(biāo)對象展辞。如下:

- (void)forwardInvocation:(NSInvocation*)invacation

此方法比較有用的實(shí)現(xiàn)方式為:在觸發(fā)消息前奥邮,先以某種方式改變消息內(nèi)容,比如追加另外一個(gè)參數(shù)罗珍,或是改換選擇器洽腺,等等

5. 消息轉(zhuǎn)發(fā)全流程

Snip20160309_1.png

6. 以完整的例子演示動態(tài)方法解析

EOCAutoDictionary頭文件

#import <Foundation/Foundation.h>

@interface EOCAutoDictionary : NSObject

@property(nonatomic,strong) NSString *string;
@property(nonatomic,strong) NSNumber *number;
@property(nonatomic,strong) NSDate *date;
@property(nonatomic,strong) id opaqueObject;

@end

EOCAutoDictionary實(shí)現(xiàn)文件

#import "EOCAutoDictionary.h"
#import <objc/runtime.h>

@interface EOCAutoDictionary ()
@property(nonatomic,strong) NSMutableDictionary *backingStore;
@end

@implementation EOCAutoDictionary

// @dynamic會阻止編譯器自動生成相關(guān)的存取方法覆旱,而由開發(fā)者自己創(chuàng)建存取方法
@dynamic string, number, date, opaqueObject;

- (id)init{
    if (self = [super init]) {
        _backingStore = [NSMutableDictionary new]; // 延遲加載
    }
    return self;
}

// 動態(tài)添加新方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString hasPrefix:@"set"]) {
        class_addMethod(self,
                        sel,
                        (IMP)autoDictionarySetter,
                        "v@:@");
    }else{
        class_addMethod(self,
                        sel,
                        (IMP)autoDictionaryGetter,
                        "@@:");
    }
    return YES;
}

// getter函數(shù)
id autoDictionaryGetter(id self, SEL _cmd){
    // 從EOCAutoDictionary對象獲取backingStore字典
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    
    // 將選擇器轉(zhuǎn)換為字符串蘸朋,并將其設(shè)為key
    NSString *key = NSStringFromSelector(_cmd);
    
    // 返回backingStore字典中key所對應(yīng)的值
    return [backingStore objectForKey:key];
}

// setter函數(shù)
void autoDictionarySetter(id self, SEL _cmd, id value){
    // 從EOCAutoDictionary對象獲取backingStore字典
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    
    // 將選擇器轉(zhuǎn)換為字符串,并將其拷貝為可變字符串
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    
    // 移除key中尾部的“:”
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
    // 移除key中前面的“set”
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    
    // 取出現(xiàn)有的key中的首字母扣唱,將其小寫化并替代掉原來的首字母
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
    // 根據(jù)key給backingStore存儲相關(guān)的值
    if (value) {
        [backingStore setObject:value forKey:key];
    }else{
        [backingStore removeObjectForKey:key];
    }
    
}
@end

main函數(shù):

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        EOCAutoDictionary *autoDict = [EOCAutoDictionary new];
        // autoDict.date == [autoDict setDate]
        // 由于接收者沒有相應(yīng)的方法可調(diào)用藕坯,因?yàn)锧dynamic特性,所以可以動態(tài)新增方法
        autoDict.date = [NSDate dateWithTimeIntervalSince1970:3140907998];
        NSLog(@"%@",autoDict.date);
    }
    return 0;
}

輸出結(jié)果為:

2016-03-09 20:29:25.552 第12條.演示動態(tài)方法解析[7000:347059] 2069-07-13 02:26:38 +0000
Program ended with exit code: 0

總結(jié):要想添加新屬性噪沙,只需要用@property來定義炼彪,并將其聲明為@dynamic即可。

要點(diǎn)

  • 若對象無法響應(yīng)某個(gè)選擇器正歼,則進(jìn)入消息轉(zhuǎn)發(fā)流程辐马。
  • 通過運(yùn)行期的動態(tài)方法解析功能,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中局义。
  • 對象可以把其無法解讀的某些選擇器轉(zhuǎn)交給其他對象(備援接收者)來處理喜爷。
  • 經(jīng)過上述兩步之后,如果還是沒辦法處理選擇器萄唇,那就啟動完整的消息轉(zhuǎn)發(fā)機(jī)制檩帐。

參考文獻(xiàn)

類型編碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市穷绵,隨后出現(xiàn)的幾起案子轿塔,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勾缭,死亡現(xiàn)場離奇詭異揍障,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俩由,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門毒嫡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幻梯,你說我怎么就攤上這事兜畸。” “怎么了碘梢?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵咬摇,是天一觀的道長。 經(jīng)常有香客問我煞躬,道長肛鹏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任恩沛,我火速辦了婚禮在扰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雷客。我一直安慰自己芒珠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布搅裙。 她就那樣靜靜地躺著皱卓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪部逮。 梳的紋絲不亂的頭發(fā)上好爬,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天,我揣著相機(jī)與錄音甥啄,去河邊找鬼。 笑死炬搭,一個(gè)胖子當(dāng)著我的面吹牛蜈漓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宫盔,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼融虽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灼芭?” 一聲冷哼從身側(cè)響起有额,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巍佑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茴迁,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年萤衰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堕义。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,435評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脆栋,死狀恐怖倦卖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椿争,我是刑警寧澤怕膛,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站秦踪,受9級特大地震影響褐捻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洋侨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一舍扰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧希坚,春花似錦边苹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聊疲,卻和暖如春茬底,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背获洲。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工阱表, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贡珊。 一個(gè)月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓最爬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親门岔。 傳聞我的和親對象是個(gè)殘疾皇子爱致,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評論 2 359

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