• Runtime常用的几个场景


    1.给分类动态添加属性

    FDFullscreenPopGesture中给UIViewController的分类里有这么一个属性:

    @property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;

    这是一个block的属性,block定义如下:

    typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);

    看到这里也许你会提问,OC中不是不能给分类添加属性么?正常情况下,OC是不允许给OC添加属性的。但是利用Runtime的特性,这是可以办到的。实现方法如下:

    - (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
    {
        return objc_getAssociatedObject(self, _cmd);// 根据关联的key,获取关联的值。这里的key等于_cmd,_cmd等于fd_willAppearInjectBlock
    }
    
    - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
    {
        // 第一个参数:给哪个对象添加关联
        // 第二个参数:关联的key,通过这个key获取
        // 第三个参数:关联的value
        // 第四个参数:关联的策略
        objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);//关联对象
    }

    动态给分类添加属性的方法是:

    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

    获取这个属性的方法是:

    objc_getAssociatedObject(id object, const void *key)

    还有一个方法是移除属性:

    objc_removeAssociatedObjects(id object)

    是的,这样就动态的给UIViewController的分类添加了fd_willAppearInjectBlock这么一个属性。

    NOTE:在使用Runtime的这些方法的时候不要忘了导入objc/runtime.h这个头文件哦!

    2.动态添加方法

    要想动态添加方法我们必须了解方法是如何执行的,通常我们调用方法是通过[object message]这种方法,除了这种方法还有一种是比较少用的,就是[object performSelector:@selector(message)]这种方式。通过下面这张图我们可以了解一下他们对消息的处理的不同之处。


    iOS消息转发.png

    通过上图,我们可以得知,要想动态添加方法必须是通过[object performSelector:@selector(message)]这种方式调用方法才能在运行时阶段通过Runtime的一些方法达到动态的添加方法。如果现在有一个Person类,在其它地方通过performSelector的方式调用Personrun方法。但是Person类中并没有实现这个方法。

    Person p = [Person alloc] init];
    // 这个时候即使Person类没有实现run方法编译器也不会报错
    [p performSelector:@selector(run)];

    这时候只需要在Person中实现resolveInstanceMethod:方法就可以达到动态添加方法的目的。

    //首先我们要在Person类里面实现我们要动态添加的方法
    // 要注意,默认方法都有两个隐式参数
    void run(id self,SEL sel){ 
        NSLog(@"%@ %@",self,NSStringFromSelector(sel));
    }
    // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
    // 刚好可以用来判断未实现的方法是不是我们想要动态添加的方法
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        //先判断一下传过来的是不是run方法
        if (sel == @selector(run)){
            //如果是run方法就动态添加run方法
            class_addMethod(self.class, @selector(run),(IMP)run, "v@:");
            // 第一个参数:给哪个类添加方法
            // 第二个参数:添加方法的方法编号
            // 第三个参数:添加方法的函数实现(函数地址),如果是OC方法
            //可以用+(IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
            // 第四个参数:方法的签名,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
        }
    }

    这样就达到了给一个类动态添加方法的效果了,如果想把方法转发给其他的类实现,需要处理消息转发的第二或第三个函数了。

    3.替换系统自带的方法

    当一些时候,系统自带效果满足不了我们的时候,要么我们自定义,要么直接替换系统的方法。在公有的API是没有方法办到的。我们来看一段FDFullscreenPopGesture的代码(注释是我加的):

    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            //获取系统方法的SEL
            SEL originalSelector = @selector(viewWillAppear:);
            //获取替换方法的SEL
            SEL swizzledSelector = @selector(fd_viewWillAppear:);
            //为了获取IMP指针,获得方法的Method
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            //为了安全起见,先判断是否已经存在要交换的方法
            BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
            if (success) {
                class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    - (void)fd_viewWillAppear:(BOOL)animated
    {
        //不要认为这句代码有错,其实很好理解,在调用这句的时候方法已经交换了
        // Forward to primary implementation.
        [self fd_viewWillAppear:animated];
    
        if (self.fd_willAppearInjectBlock) {
            self.fd_willAppearInjectBlock(self, animated);
        }
    }

    通过上面的代码我们可以看出来,替换系统自带的方式实现需要用到的重要方法是method_exchangeImplementations()方法,并且要注意替换方法里面对自己的调用。这个方法也就是人们常说的Method Swizzling黑魔法,用的时候要注意,这是一把双刃剑!

    结尾

    Runtime在项目中很少用,但是要理解它,理解了之后用起来也不危险。如果你喜欢我的文章,不妨扫一扫下面的二维码请我喝杯茶。祝大家在iOS开发的道路上玩得愉快!

  • 相关阅读:
    字符编码乱码处理
    字典,元组,集合的使用
    三级菜单 ,求1
    运算符 if和while的使用
    if条件判断和while循环
    基本数据类型的结构和使用方法
    计算机基础部分
    计算机基础
    数据库之表查询,单表、多表,子查询
    google map API 学习
  • 原文地址:https://www.cnblogs.com/weijie-1/p/6268084.html
Copyright © 2020-2023  润新知