OC 中的動態(tài)綁定和消息轉(zhuǎn)發(fā)

動態(tài)綁定

我們都知道OC是動態(tài)語言趁耗,表現(xiàn)為對象方法的調(diào)用實(shí)際上是對對象發(fā)送消息,編譯時不確定這個對象執(zhí)行什么方法疆虚,而在運(yùn)行時由消息(方法選擇器selector決定對象執(zhí)行什么方法)苛败,這種消息發(fā)送的方式叫做動態(tài)綁定。通過編譯我們知道径簿,發(fā)送消息的底層API是objc_msgSend這個c函數(shù)罢屈,其工作流程大概是這樣的(為了方便參考,這里也給出了相關(guān)數(shù)據(jù)結(jié)構(gòu)的源碼):

// 類的數(shù)據(jù)結(jié)構(gòu)
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;

//  方法的定義
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
  1. 獲取傳入對象所屬的類篇亭。
  2. 使用傳入的selector在緩存cache列表中查詢缠捌。
  3. 如果找到了方法,則通過Method中的IMP(方法實(shí)現(xiàn)的地址)去調(diào)用方法译蒂。
  4. 如果緩存中不存在曼月,則開始在methodLists列表中查找。
  5. 如果methodLists列表找不到柔昼,利用super_class指針去父類查找哑芹,
  6. 遞歸地查找父類,一直查找到NSObject捕透,如果還是找不到聪姿,則觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制碴萧。

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

簡單的說,就是當(dāng)對象通過selector找不到方法的時候末购,還有三次機(jī)會通過其他方式調(diào)用方法破喻,避免程序崩潰(unrecognized selector),這三次機(jī)會分別是:

  1. 動態(tài)方法解析:看消息接受者是否能動態(tài)添加方法
  2. 備援的接收者:先看其他有沒有對象能處理這條消息
  3. 消息重定向:將所有信息封裝到 NSInvocation 對象中處理

動態(tài)方法解析盟榴,其實(shí)就是調(diào)用對象類中的以下方法:

+ (BOOL)resolveInstanceMethod:(SEL)selector;  //當(dāng)遇到無法解讀的實(shí)例方法時調(diào)用這個方法
+ (BOOL)resolveClassMethod:(SEL)selector;     //當(dāng)遇到無法解讀的類方法時調(diào)用這個方法

例子:

@interface Person : NSObject
@end
  
@implementation Person

- (void) implementAppendString:(NSString *)aString {
    NSLog(@"%@", aString);
}

+ (BOOL) resolveInstanceMethod:(SEL)selName {
    
    if (selName == @selector(speak:)) {
        class_addMethod([self class], selName, class_getMethodImplementation([self class], @selector((implementSpeak:))), "v@:");
        return YES;
    }

    return [super resolveInstanceMethod:selName];
}

@end
 
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        id person = [[Person alloc] init];
        [person performSelector: @selector(speak:) withObject:@"hello"];
    }
    return 0;
}

備援的接收者曹质,當(dāng)動態(tài)方法解析失敗時(返回NO),可以通過以下方法詢問在其他對象中可否處理消息曹货,如果找到接收者咆繁,則返回接收者的對象讳推,否則返回nil

- (id)forwardingTargetForSelector:(SEL)aSelector;

例子:

@interface Person : NSObject
@end

@interface Father : NSObject
- (void)speak:(NSString *)aString;
@end


@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return [[Father alloc] init];
}

@end

@implementation Father
- (void)speak:(NSString *)aString {
    NSLog(@"%@", aString);
}
@end
  
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        id person = [[Person alloc] init];
        [person performSelector: @selector(speak:) withObject:@"hello"];  // hello
    }
    return 0;
}

消息重定向顶籽,當(dāng)沒有備援接收者時,就只剩下最后一次機(jī)會银觅,那就是消息重定向礼饱。這個時候runtime會將未知消息的所有細(xì)節(jié)都封裝為NSInvocation對象,然后先后調(diào)用下述方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation: (NSInvocation*)invocation;

例子:

@interface Person : NSObject
@end
  
@implementation Person

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(speak)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    if (invocation.selector == @selector(speak)) {
        NSLog(@"%s", __func__); 
    }
}

@end
  
int main(int argc, const char *argv[])
{
    @autoreleasepool {
        id person = [[Person alloc] init];
        [person performSelector: @selector(speak)]; // -[Person forwardInvocation:]
    }
    return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末究驴,一起剝皮案震驚了整個濱河市镊绪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洒忧,老刑警劉巖蝴韭,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異熙侍,居然都是意外死亡榄鉴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門蛉抓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庆尘,“玉大人,你說我怎么就攤上這事巷送∈患桑” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵笑跛,是天一觀的道長付魔。 經(jīng)常有香客問我,道長飞蹂,這世上最難降的妖魔是什么抒抬? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮晤柄,結(jié)果婚禮上擦剑,老公的妹妹穿的比我還像新娘妖胀。我一直安慰自己,他們只是感情好惠勒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布赚抡。 她就那樣靜靜地躺著,像睡著了一般纠屋。 火紅的嫁衣襯著肌膚如雪涂臣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天售担,我揣著相機(jī)與錄音赁遗,去河邊找鬼。 笑死族铆,一個胖子當(dāng)著我的面吹牛岩四,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哥攘,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼剖煌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逝淹?” 一聲冷哼從身側(cè)響起耕姊,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栅葡,沒想到半個月后茉兰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欣簇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年规脸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片醉蚁。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡燃辖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出网棍,到底是詐尸還是另有隱情黔龟,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布滥玷,位于F島的核電站氏身,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惑畴。R本人自食惡果不足惜蛋欣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望如贷。 院中可真熱鬧陷虎,春花似錦到踏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凿掂,卻和暖如春伴榔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庄萎。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工踪少, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糠涛。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓援奢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脱羡。 傳聞我的和親對象是個殘疾皇子萝究,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359