美文网首页
runtime详解

runtime详解

作者: 凡克斯 | 来源:发表于2020-03-15 21:41 被阅读0次

    runtime是一套C语言API,是在程序运行时,负责将OC代码动态转化成C代码,其中最主要的是"消息机制", 我们知道C语言的函数调用,在编译时期就决定调用那个函数了,OC在编译时期并不能决定真正调用那个函数,只有在真正运行的时候才会根据函数名称找到对应函数来调用

    头文件 #import <objc/message.h>, 一般不会直接导入<objc/runtime.h>

    使用场景

    动态交换两个方法的实现

    当第三方框架或者原生方法不能满足需求的时候,我们可以在保持系统原有方法的功能的基础上,添加额外功能
    需求: 加载图片直接使用[UIImage imageNamed:@"name"]是无法知道图片有没有加载成功的,给系统的imageNamed添加额外功能

    方案一:继承系统的类,重写方法(弊端:每次都需要导入)
    方案二:使用runtime,交换方法
    实现步骤:
    给系统的方法添加分类
    自己实现一个带有扩展功能的方法
    交换方法,只需要交换一次
    @implementation UIImage (imageName)

    /**
    load方法: 把类加载进内存的时候调用, 只会调用一次
    */

    • (void)load {
      //1,先获取imageNamed方法地址
      // class_getClassMethod(获取某个类的方法)
      Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
      // 2. 获取自定义的my_imageNamed方法地址
      Method my_imageNamedMethod = class_getClassMethod(self, @selector(my_imageNamed:));
      // 3. 交换方法地址, 相当于交换实现
      method_exchangeImplementations(imageNamedMethod, my_imageNamedMethod);
      }

    //加载图片 + 判断是否加载成功

    • (UIImage *)my_imageNamed:(NSString *)name {
      UIImage *image = [UIImage my_imageNamed:name];
      if (image){
      NSLog(@"名字:%@ ---> 加载成功", name);
      }else{
      NSLog(@"名字:%@ ---> 加载失败", name);
      }

      return image;
      }

    @end

    动态添加属性

    原理:给一个类添加属性,其实本质就是给这个类添加关联, 并不是直接把这个值得内存空间添加到类的内存空间

    @interface UIImage (imageName)

    //@peoperty分类: 只会生成get, set方法声明, 不会生成实现,也不会生成下划线成员属性
    @property (nonatomic, strong) NSString *name;

    @end

    • (void)setName:(NSString *)name {
      // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
      // object:给哪个对象添加属性
      // key:属性名称
      // value:属性值
      // policy:保存策略
      objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    • (NSString *)name {
      return objc_getAssociatedObject(self, @"name");
      }
      实现字典转模型的自动转换

    思路: 利用运行时,遍历模型中的所有属性, 根据模型的属性名,去字典中查找对应的值,给模型的属性赋值
    注意:

    当字典的key和模型的属性匹配不上
    模型中嵌套模型
    数组中装着模型
    发送消息

    objc_msgSend(obj,@selector(makeText));

    动态添加方法

    @implementation Person
    // 没有返回值,1个参数
    // void,(id,SEL)
    void aaa(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"跑了%@米", meter);
    }

    // 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
    // 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
    // 作用:动态添加方法,处理未实现

    • (BOOL)resolveInstanceMethod:(SEL)sel {
      // [NSStringFromSelector(sel) isEqualToString:@"run"];
      if (sel == NSSelectorFromString(@"run:")) {
      // 动态添加run方法
      // class: 给哪个类添加方法
      // SEL: 添加哪个方法,即添加方法的方法编号
      // IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址))
      // type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, sel, (IMP)aaa, "v@:@");
      return YES;
      }
      return [super resolveInstanceMethod:sel];
      }
      @end

    什么是method swizzling(就是方法交换)

    实现交换的几种方式

    利用method_exchangeImplementations交换两个方法实现
    利用class_replaceMethod替换方法的实现
    利用method_setImplementation来直接设置某个方法的IMP
    Q1 OBJC在向一个对象发送消息时,发生了什么?

    A: 根据对象的isa指针找到类对象,查询类对象的methodlists方法列表, 如果没有找到,就沿着superClass,寻找父类, 在父类methodLists方法列表中查询,最终找到SEL,根据id和SEL确认IMP(方法实现),
    A: 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误

    Q2 什么时候会报unrecognized selector错误? iOS有哪些机制避免走到这一步?

    A1: 当发送消息时, 会根据类里面的methodLists方法列表查询要调用的方法, 当查询不到时,会一直沿着父类查询,若最终查询不到的时候会报unrecognized selector错误,当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态添加方法,若没有动态添加会使用转发-(id)forwardingTargetForSelector:(SEL)aSelector, 来保证不会崩溃

    Q3: runtime如何实现weak变量的自动置nil?

    A: runtime对注册的类, 会进行布局, 对于weak对象会放入一个hash表中, 用weak指向的对象内存地址作为key, 若这个对象的引用计数为0时, 就会用这个key在表中找到这个对象,并置为nil

    runtime防止重复点击

    思路:

    创建UIButton分类
    为分类添加属性(点击时间 和 点击时间间隔 两个时间)
    利用runtime的method_exchangeImplementations交换方法, 交换的是UIButton的响应点击时间sendAction:to:forEvent:
    自定义方法中判断, 如果当前时间 - 点击的时间 < 时间间隔就return
    @interface UIButton (CS_FixMultiClick)
    @property (nonatomic, assign) NSTimeInterval cs_acceptEventInterval; // 重复点击的间隔
    @property (nonatomic, assign) NSTimeInterval cs_acceptEventTime;
    @end

    import "UIControl+CS_FixMultiClick.h"

    import <objc/runtime.h>

    @implementation UIButton (CS_FixMultiClick)
    // 因category不能添加属性,只能通过关联对象的方式。
    static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";

    • (NSTimeInterval)cs_acceptEventInterval {
      return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
      }

    • (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval { objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
      static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

    • (NSTimeInterval)cs_acceptEventTime {
      return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
      }

    • (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime { objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
      // 在load时执行hook

    • (void)load {
      Method before = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
      Method after = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));
      method_exchangeImplementations(before, after);
      }
    • (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
      if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
      return;
      }
      if (self.cs_acceptEventInterval > 0) {
      self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
      }
      [self cs_sendAction:action to:target forEvent:event];
      }
      @end

    runtime实现空白页

    交换UITableView, UICollectionView, UIWebView的reloadData方法, 加判断添加空白页

    相关文章

      网友评论

          本文标题:runtime详解

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