zoukankan      html  css  js  c++  java
  • 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开发的道路上玩得愉快!

  • 相关阅读:
    【NOIP2007】守望者的逃离
    20200321(ABC)题解 by 马鸿儒 孙晨曦
    20200320(ABC)题解 by 王一帆
    20200319(ABC)题解 by 王一帆 梁延杰 丁智辰
    20200314(ABC)题解 by 董国梁 蒋丽君 章思航
    20200309(ABC)题解 by 梁延杰
    20200307(DEF)题解 by 孙晨曦
    20200306(ABC)题解 by 孙晨曦
    20200305(DEF)题解 by 孙晨曦
    20200303(ABC)题解 by 王锐,董国梁
  • 原文地址:https://www.cnblogs.com/weijie-1/p/6268084.html
Copyright © 2011-2022 走看看