美文网首页
Runtime的总结(第二篇)

Runtime的总结(第二篇)

作者: 杭子_ | 来源:发表于2017-11-21 01:48 被阅读4次

    利用Runtime实现方法交换

    分析场景     在我们的项目开发中,会遇到这样的问题:如果字符串链接里面带有文字,那它返回的url是空的, “null” ,后面我们再进行网络请求一个空的url结果..... 。这也就说明NSURL 在创建出来后,系统不会检测是否为nil。怎么解决?

    开始的想法:我可以为NSURL扩展一个方法,在方法实现里判断 返回的url是否为空,再做一些其他处理,最后再把方法名替换下就行啦。   扩展的方法如下;

    +(instancetype)HZ_urlWtihString:(NSString *)str{

    NSURL *url = [NSURL URLWithString:str];

    if (url == nil) {

    NSLog(@"url 空了");

    }

    return url;

    }

    要是方法调用次数少,简单替换下方法名 。但是如果类似于这种情况 并且替换的地方很多呢?就要一个一个找替换 很麻烦。  

    这时有一个一劳永逸的方法:HOOK(面向切面编程)钩子思想!! 勾住某个方法的调用,改变方法调用的顺序。

    继续说,我们可以不改变系统方法的调用,还是调用URLWithString,但是调用后让它走到我们自定义方法的实现里。 方法的调用就是发送消息 , 那么调用URLWithString方法 被编译后C++代码 就是这样:   objc_msgSend(<#id self#>, @selector(URLWithString)) ,只要他发送这消息,我就拦截到  ,接下来就需要我们做些改变 !! Runtime 提供了改变方法调用顺序的功能  ,那改变的是什么?  @selector(URLWithString)这是被编译后的我们无法改变(改变相当于改变源码),在@selector之后还有一个机会改变方法的调用 ,改变的是方法的IMP

    提示方法的组成:SEL方法编号+IMP方法实现(实质:函数指针,它指向一块内存区域,内存里面的二进制指令)),SEL和IMP是一一对应的。 也可以这样理解:SEL(书的目录)--IMP(书的页码)--  代码(书里的内容);我们发送消息SEL 它就会找到IMP(IMP不是代码 它是函数指针)

    发送消息就是通过目录找页码,通过页码找内容

    Runtime可以这么做,它可以改变目录上面的对应关系,它可以做交换

    那什么时候交换?要在一切调用该方法之前做好,main函数里一般不写,我们可以在分类的.m源文件里,源文件再加载的时候有个+(void)load方法,

    //App装载进内存的时候 就会执行这个指令;(app安装在手机里都是二进制,点开App第一件事就是将硬盘里的二进制装进内存里,就会读load里面的内容,也就是先执行load里面的指令,再启动APP 执行UIApplicationmain, load比main函数靠前)

    +(void)load{

    //下钩子

    }

    我们在load里面“下钩子”。我们会用到这个方法来交换:

    method_exchangeImplementations(<#Method m1#>, <#Method m2#>)//改变方法实现

    我们通过函数获取m1 m2

    +(void)load{

    //下钩子

    //    class_getInstanceMethod;//获取实例方法

    //获取类方法,返回Method结构体

    Method urlWithString = class_getClassMethod(self, @selector(URLWithString:));

    Method HZ_UrlWithString = class_getClassMethod(self, @selector(HZ_urlWtihString:));

    //交换方法的实现(相当于交换:“书的页码”)

    method_exchangeImplementations(urlWithString, HZ_UrlWithString);

    }

    到现在,方法交换就实现啦,原来的代码不用动。但是还有一地方没改,否则会造成循环调用 ,栈溢出:URLWithString 改为HZ_urlWtihString

    //HZ_urlWtihString 与 urlWithString 的imp已经交换

    +(instancetype)HZ_urlWtihString:(NSString *)str{

    NSURL *url = [NSURL HZ_urlWtihString:str];

    if (url == nil) {

    NSLog(@"url 空了");

    }

    return url;

    }

    所谓HOOK,是一种思想,是专门的一种钩子,他会在内存中找到某一个方法的调用(一块儿内存区域),直到调用这个方法时,就勾住他,对它的方法行为进行改变,所以Runtime 的方法交换 某种意义上说也是一种钩子。

    动态添加方法

    当一个类被调用了没有被实现的方法,那么他会走这两种方法的一种

    //当类被调用没有实现的类方法时,调用该方法

    +(BOOL)resolveClassMethod:(SEL)sel{

    }

    //当这个类被调用没有实现的对象方法时,调用该方法

    +(BOOL)resolveInstanceMethod:(SEL)sel{

    }

    比如在控制器里,一个类Person被调用了一个没有实现的无参数方法[p performSelector:@selector(eat)];,会走到resolveInstanceMethod:里来,那么我们就可以在该方法中给Person类动态添加方法。使用Runtime添加方法,就会用到这个函数class_addMethod,代码实例

    //当这个类被调用没有实现的对象方法时,调用该方法

    +(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%@",NSStringFromSelector(sel));

    //动态添加方法(方法组成:SEL (方法编号) + IMP(函数指针) + 函数体)

    /*

    1.cls 目标类

    2.sel 方法编号

    3.imp 方法实现(函数指针)

    4.返回值类型 (无返回值无参的函数 为 "v@:")//v代表viod,@代表一个id对象,:代表方法编号

    */

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

    //不知道返回什么,父类返回什么 我这就返回什么

    return [super resolveInstanceMethod:sel];

    }

    //写一个函数,haha 就是函数指针,这里需要加(IMP)来强制转化,不然会报警告

    void haha(){

    NSLog(@"来了!!");

    }

    以上时添加的无返回值无参数的实例方法,如果调用了一个没有实现的带参数的方法,

    [p performSelector:@selector(eat) withObject:@"哈哈"];

    这就要改变class_addMethod的第四个参数-返回值类型,和haha函数体里的参数。返回值类型改为“v@:”,更改函数体里的参数,有两个 方法调用的隐式参数 id self,SEL _cmd,

    /*

    1.方法的调用者

    2.方法的编号

    (OC 里 每个方法调用,它的函数体里有俩个隐式参数,是默认的)

    */

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

    NSLog(@"%@",str);

    }

    为什么会有隐式参数,可能隐约感觉的到,方法调用就是消息发送:

    // [p performSelector:@selector(eat) withObject:@"哈哈"];

    //调用OC方法的时候,会给IMP传入两个参数:方法的调用者  id self 、方法编号SEL _cmd (行参名字可以改)

        objc_msgSend(p, @selector(eat:),@"哈哈");

    这里面的p 和 @selector(eat:) 就传到 void eat(id self,SEL _cmd,NSString *str)这里来啦。

    *另外swift调用方法 和oc  调用方法的底层是不一样的。

    相关文章

      网友评论

          本文标题:Runtime的总结(第二篇)

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