OC Runtime之动态方法解析和消息转发

OC Runtime之动态方法解析和消息转发

转发:链接

首先直接上代码:

@interface Person : NSObject 

- (void)eat; 

- (void)say:(NSString*)world; 

@end 

@implementation AA 

- (void)eat { 

  NSLog(@"eat"); 

@end

int main (int argc, const charchar * argv[]) { 

    @autoreleasepool { 

        Person * p = [Person new]; 

        [p eat]; 

        [p say:@”Hello”];   

    } 

    return 0; 

以上代码执行后会产生什么样的结果?我想大家都应该很清楚,那就是程序会crash,因为我们并未实现方法say:,在Person及其父类的method list中都找不到其相应实现,所以会crash。错误原因:-[Person say:]: unrecognized selector sent to instance 0x100100020

那么怎么解决这样的问题呢,答案就是动态方法解析(Dynamic Method Resolution)和消息转发(Message Forwarding)。

动态方法解析(Dynamic Method Resolution)

你可以动态地提供一个方法的实现。例如我们可以用@dynamic关键字在类的实现文件中修饰一个属性:

@dynamic propertyName;

关键字dynamic的作用是告诉编译器与属性相关的方法将在运行时动态提供,编译器不需生成对应的getter

setter方法(原文:tells the compiler that the methods associated with the

property will be provided

dynamically)。我们可以通过分别重载resolveInstanceMethod:和resolveClassMethod:方法分别添加实例方法实现和类方法实现。因为当

Runtime

系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用resolveInstanceMethod:或resolveClassMethod:来给程序员一次动态添加方法实现的机会。我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作:

void dynamicMethodIMP(id self, SEL _cmd) {

    // implementation ....

}

@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

PS:动态方法解析会在消息转发机制浸入前执行。如果 respondsToSelector: 或

instancesRespondToSelector:方法被执行,动态方法解析器将会被首先给予一个提供该方法选择器对应的IMP的机会。如果你实想让该方法选择器通过转发机制转发,那么就让resolveInstanceMethod:返回NO。

消息转发(Message Forwarding)

给某个对象发送无法处理的消息时会产生错误。幸运的是在错误报出之前,runtime系统给了这个对象第二次处理这个消息的机会(the

runtime system gives the receiving object a second chance to handle the

message)。

重定向

在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的接受者为其他对象:

- (id)forwardingTargetForSelector:(SEL)aSelector

{

    if(aSelector == @selector(mysteriousMethod:)){

        return alternateObject;

    }

    return [super forwardingTargetForSelector:aSelector];

}

如果此方法返回nil或self,则会进入消息转发机制(forwardInvocation:);否则将向返回的对象重新发送消息。

转发

当动态方法解析不作处理返回NO时,消息转发机制会被触发。在这时forwardInvocation:方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

    if ([someOtherObject respondsToSelector:

            [anInvocation selector]])

        [anInvocation invokeWithTarget:someOtherObject];

    else

        [super forwardInvocation:anInvocation];

}

该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。

这里需要注意的是参数anInvocation是从哪的来的呢?其实在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法,否则会抛异常。

当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。通过实现我们自己的forwardInvocation:方法,我们可以在该方法实现中将消息转发给其它对象。

forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。

注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。

所以,如果我们希望一个对象将negotiate消息转发给其它对象,则这个对象不能有negotiate方法。否则,forwardInvocation:将不可能会被调用。

整个消息处理过程如下图所示:

步骤1、2、3都是在没有实现msg方法的情况下,runtime为我们提供的补救的机会。

针对文章开始崩溃的问题,可以用三种方式补救:

第一种:resolveInstanceMethod:

// c形式

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(say:)) {

        class_addMethod([self class], sel, (IMP)say, "v@:*");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

void say(id self, SEL _cmd, NSString *str) {

    NSLog(@"Person say:%@", str);

}

//OC形式

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(say:)) {

        class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(sayMethodIMP:)), "v@:*");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

- (void)sayMethodIMP:(NSString *)str {

    NSLog(@"Person say:%@", str);

}

第二种:forwardingTargetForSelector:这里增加了Mobile类来相应say:消息。

//Mobile.h

#import <Foundation/Foundation.h>

@interface Mobile : NSObject

- (void)say:(NSString*)world;

@end

//Mobile.m

#import "Mobile.h"

@implementation Mobile

- (void)say:(NSString*)world {

    NSLog(@"Mobile say:%@", world);

}

@end

- (id)forwardingTargetForSelector:(SEL)aSelector {

    if (aSelector == @selector(say:)) {

        return [Mobile new];

    }

    return [super forwardingTargetForSelector:aSelector];

}

第三种:forwardInvocation:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    if (aSelector == @selector(say:)) {

        return [NSMethodSignature signatureWithObjCTypes:"V@:*"];

    }

    return [super methodSignatureForSelector:aSelector];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    SEL selector = [anInvocation selector];

    Mobile *mobile = [Mobile new];

    if ([mobile respondsToSelector:selector]) {

        [anInvocation invokeWithTarget:mobile];

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342