Runtime消息转发机制

来源:互联网 发布:淘宝csgo国际服 编辑:程序博客网 时间:2024/05/17 23:38

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

原创粉丝点击