前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Runtime消息转发机制

Runtime消息转发机制

作者头像
星宇大前端
发布2019-01-15 15:42:05
7650
发布2019-01-15 15:42:05
举报
文章被收录于专栏:大宇笔记

iOS 消息发送机制

首先要知道Runtime的时候类的结构:

struct objc_class {

Class_Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

    Class _Nullable super_class                              OBJC2_UNAVAILABLE;

const char *_Nonnull name                               OBJC2_UNAVAILABLE;

long version                                             OBJC2_UNAVAILABLE;

long info                                                OBJC2_UNAVAILABLE;

long instance_size                                       OBJC2_UNAVAILABLE;

struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;

struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;

struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;

struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

      通过之前的博客我们知道了,iOS 的方法调用,在运行时的时候会是给某个对象发消息,然后在这个类的MethodList里取寻找有没有调用的这个方法。

      那么问题来了,如果我们给一个对象发送消息的时候(即调用该对象的方法),这个方法没在这个对象的MethodList中找到,那么会怎么样?

想必大家都已经知道结果了,那就是遇到我们最熟悉的Crash。

   unrecognized selector sent to instance

      这时候这个被调用的哥们可能会骂你,你TM有病啊,你自己没有给我定义这个方法你TM还给我发消息。

     没办法,哥们,我真的需要你来给我做这件事。你可以找你的兄弟去帮忙,然后给我结果就好了。

     然后这哥们就告诉了你,他找不到方法的时候,应该怎么能拿到结果。

iOS对象找不到方法,系统调用机制

  1. 调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
  2. 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
  3. 调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
  4. 调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
  5. 调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

iOS如何消息转发

1.首先在类方法列表中没有找到方法,那么系统会调用resolveInstanceMethod或者resolveClassMethod,让你动态添加方法实现。

/**

 *  通过这个方法来实现动态添加方法

 *

 *  @param sel 没有实现的方法

 *

 *  @return 返回YES处理方法或者NO转发到下一步

 */

+(BOOL)resolveInstanceMethod:(SEL)sel{

//方法名

NSString *selStr = NSStringFromSelector(sel);

if ([selStr isEqualToString:@"XXXX1"]) {

//增加你要实现的方法

class_addMethod(self, sel, (IMP)AAAA, "@@:");

return YES;

    }

if ([selStr isEqualToString:@"XXXX2:"]) {

class_addMethod(self, sel, (IMP)BBBB, "v@:@");

return YES;

    }

return [super resolveInstanceMethod:sel];

}

/**

 *  这个方法实现XXXX2的转发

 *

 *  @param self  对象

 *  @param cmd   方法

 *  @param value 传入的值

 */

void BBBB(idself, SEL cmd,id value){

}

/**

 *  这个方法用于XXXX1的转发

 *

 *  @param self 对象自己

 *  @param cmd  方法名

 *

 *  @return 返回得到的值

 */

id AAAA(idself, SEL cmd){

}

顺便说一下: class_addMethod方法的使用

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

参数说明:

cls:被添加方法的类

name:可以理解为方法名,这个貌似随便起名,比如我们这里叫sayHello2

imp:实现这个方法的函数

types:一个定义该函数返回值类型和参数类型的字符串,

types具体符号讲解:

例如 AAAA的参数 @@:

按顺序分别表示:

第一个参数@    表示返回值为id     

返回类型int 用 i 表示 , 返回void用v表示

第二个参数@    表示参数self    

还可以表示OC类型的参数

第三个参数:    表示SEL(_cmd)

2.如果第一个方法返回NO,转发进入下一步forwardingTargetForSelector

/**

 *  转发到另一个对象去处理,其他的下一步

 *

 *  @param aSelector 方法

 *

 *  @return 返回转发的处理对象或者nil

 */

-(id)forwardingTargetForSelector:(SEL)aSelector{

NSString *selStr =NSStringFromSelector(aSelector);

//如果是没有实现的方法,则处理转发

if ([selStrisEqualToString:@"Method1"]) {

//返回处理这个转发的对象

return MethodModel;

    }else{

return [superforwardingTargetForSelector:aSelector];

    }

}

3.如果没有转发对象,上一步返回未nil,则进行下一步转发。如果返回nil,doesNotRecognizeSelector报异常。

/**

 *  是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。

 *

 *  @param aSelector 方法名

 *

 *  @return 返回一个签名

 */

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

NSMethodSignature *sig =nil;

NSString *selStr = NSStringFromSelector(aSelector);

//判断你要转发的SEL

if ([selStr isEqualToString:@"Method2"]) {

//此处返回的sig是方法forwardInvocation的参数anInvocation中的methodSignature

//为你的转发方法手动生成签名

        sig = [Method2Model methodSignatureForSelector:@selector(Method2)];

    }else{

        sig = [super methodSignatureForSelector:aSelector];

    }

return sig;

}

/**

 *  转发方法打包转发出去

 *

 *  @param anInvocation

 */

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

NSString *selStr = NSStringFromSelector(anInvocation.selector);

if ([selStr isEqualToString:@"Method2"]) {

//设置处理转发的对象

        [anInvocation setTarget:self.companyModel];

//设置转发对象要用的方法

        [anInvocation setSelector:@selector(Method2:)];

BOOL Argument =YES;

//第一个和第二个参数是target和sel

        [anInvocation setArgument:&Argument atIndex:2];

        [anInvocation retainArguments];

        [anInvocation invoke];

    }else{

        [super forwardInvocation:anInvocation];

    }

}

Demo地址:https://github.com/RainManGO/Runtime-Message-Forwarding

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年07月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • iOS 消息发送机制
  • iOS对象找不到方法,系统调用机制
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档