美文网首页
认识Runtime运行时机制

认识Runtime运行时机制

作者: w_xiao_wu | 来源:发表于2018-03-21 14:49 被阅读0次

    OC方法的本质

    首先了解OC方法的本质到底是什么:

    OC方法由两个部分组成:

    SEL: 方法编号(一本书的目录编号)
    IMP: 方法实现,是函数指针,指向函数(一本书的目录页码,页码指向对应页的内容)

    动态绑定

    简单举个例子:一个Person类的.m文件中不实现-(void)eat:(NSString *)
    通过运行时来动态实现这个eat方法,这个过程叫做 动态绑定

     #import <objc/message.h>
     
     +(BOOL)resolveInstanceMethod:(SEL)sel{
        // 给类添加eat方法,IMP==eat
        if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
            //self:方法调用者,sel:方法编号,eat:(IMP函数指针)方法实现
            class_addMethod(self, sel, eat, “”);
        }
        return [super resolveInstanceMethod:sel];
      }
      
     // 实现c函数eat()
     void eat(id self, SEL _cmd, NSString *objc){
        NSLog(@”我来了%@”, objc);
     }
    

    OC方法调用 会传递两个 隐式参数 self, _cmd,self 是方法的调用者,_cmd 是方法编号;
    OC的方法调用其实是 消息发送 (通过终端clang –rewrite-objc main.m,生成一个.cpp文件,可以看到.m文件的底层实现。)

    Person *p = [[Person alloc]init];   
    //[P eat:@”汉堡”]; 的底层就是objc_msgSend函数
    objc_msgSend(P, @selector(eat:), @”汉堡”);
    

    消息转发

    重定向

    当对象的方法签名在头文件中暴漏出来,而在.m文件中忘记实现,一般程序会报运行时错误不识别的选择器,通过消息转发可以改变这行为。

    消息转发: 当对象接收到与其 方法集 不匹配的消息时,通过消息转发机制可以使对象执行用户预先定义的逻辑,如:将消息发送给能够做出响应的其他接收器(对象),或者将所有无法识别的消息都发送给同一个
    接收器 再或者 默默的吞下消息(既不执行处理过程也不使程序抛出运行时错误)。

    还是上面的例子,在Dog类中实现了eat方法,在Person类中可以通过 消息转发 让Dog去相应eat(我吃不了,dog你帮我吃吧)

    //消息重定向
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        if ([_dog respondsToSelector:aSelector]) {  
            return _dog;     // 相当于 [_dog performSelector:aSelector];  
        }   
        // 给nil发消息          
       return nil; 
    }
    

    方法签名

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {  
        if(aSelector == @selector(eat:)) {  
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; // v@:@ (type encoding)  
        }  
        return nil;  
    }  
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {  
        if (anInvocation.selector == @selector(eat)) {  
            Dog *dog = [[Dog alloc] init];  
            [anInvocation invokeWithTarget:dog];  
        }  
    } 
    

    重写methodSignatureForSelector:和forwardInvocation:方法,将方法签名,转发给真正实现了该方法的目标对象,让其去调用已实现的方法。

    methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash
    forwardInvocation:将选择器转发给一个真正实现了该消息的对象。

    1.forwardingTargetForSelector同为消息转发,但在实践层面上有什么区别?何时可以考虑把消息下放到forwardInvocation阶段转发?

    forwardingTargetForSelector 仅支持一个对象的返回,也就是说消息只能被转发给一个对象。比如转发给一个专门用于处理未识别的方法的处理类。
    forwardInvocation 可以将消息同时转发给任意多个对象,如果你想执行其他逻辑(如记录日志并吞下该消息),可以考虑用 forwardInvocation

    • 关于 signatureWithObjCTypes: 中的objcTypes,是OC的类型编码 Type Encodings【相关文档链接
    • 语法参照图:

    image.png

    前面提到OC方法调用 会传递两个 隐式参数 self, _cmd,self 是方法的调用者,_cmd 是方法编号,指向方法本身。

    例如:-(void)eat:(NSString *)food;实际上有三个参数:self, _cmd和food。

    eat: 转ObjcTypes为:
    "返回值类型 第一参数 第二参数 [第三参数...]"
    如果没有返回值用v,如果有用@代替id类型,
    第一二参数是必须存在的,即 id 类型的 self,和 SEL 类型的 _cmd,第三参数是用户自定义的参数,可有可无

    例如: "v@:@" : void id类型的self SEL类型的_cmd 自定义参数;
    "@@:" :id类型的返回值 id类型的self SEL类型的_cmd

    因此我们可以调用[anInvocation getArgument: atIndex:] 获取指定的参数值

    Runtime应用场景—HOOK(钩子)

    HOOK,方法欺骗

    直接上例子:

    /*当url中含有中文时(需要转码),request还是能创建,但是此时  
      request中的url为空,request的创建方法没有检测url为空的情况,  
      很容易出现难以定位的bug(Swift中有可选类型,可以避免这问题)。
    */
    NSURL *url = [NSURL URLWithString:@”http://www.baidu.com/中文”];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    

    解决:①用 category 创建分类 NSURL+HOOKXW_URLWithString 方法,将用到 URLWithString 的地方替换成我们自己的方法 XW_URLWithStringXW_URLWithString 保留了 URLWithString 原本内部创建 url 的方式,添加 url 是否为空的判断。
    不足:每次都要去导入头文件,每次都要去替换项目中原本的urlWithString方法,比较麻烦;

    ②利用runtime运行时,改变方法调用的顺序。
    OC发送 URLWithString 消息会对应的的去找这个方法的实现,用运行时可以去改变这种一一对应的关系,
    只要HOOKURLWithString 这个方法的调用,当发送 URLWithString(SEL)消息时,让它去找 XW_URLWithString(IMP)这个实现。

    NSURL+HOOK.m 文件中在 +(void)load 方法中下钩子HOOK,以交换方法的IMP实现。

    #import<objc/runtime.h>
    
    +(void)load {
        //获取method
        Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:)); 
        Method XWURLWithStr = class_getClassMethod(self, @selector(XW_URLWithString:));
        //交换方法的IMP
        method_exchangeImplementations(URLWithStr, XWURLWithStr);
    }
    

    外界不需要导入 NSURL+HOOK.h ,也不需要修改 URLWithStringXW_URLWithString 就能直接把 URLWithString 方法实现替换。

    XW_URLWithString 实现如下:

    +(void) XW_URLWithString:(NSString*)URLString{
        // 保留系统原本的实现,实现交换后这里不能用URLWithString,否则会递归
        NSURL *url = [NSURL XW_URLWithString:URLString];
        if(url == nil){
            NSLog(@"空了");
        }
        return url;
    }

    相关文章

      网友评论

          本文标题:认识Runtime运行时机制

          本文链接:https://www.haomeiwen.com/subject/xlpwqftx.html