Runtime(三)方法交换

作者: 2d9cba783f86 | 来源:发表于2018-08-19 22:32 被阅读23次

    Runtime(三)方法交换

    在刚开始关注Runtime时, 不知道小伙伴们是否听过一种传说

    RuntimeObjective-C黑魔法

    它怎么就黑魔法了, 通过前两节的讲述, 并没有发现呀.
    其实众说纷纭的黑魔法就是Runtime的另一项技能, 也是这篇文章要说的方法交换, 英文名MethodSwizzling

    加载顺序

    在说方法交换之前先科普一下, 加载顺序, 在我们项目target下的Build Phases->Compile Sources里都是项目内所有的.m文件, 这里的每一个.m文件在初始装载时都会调用一个类方法, +(void)load类方法, 这个可是默认加载的哦. 我们交换方法就是在这个类方法里进行交换.

    方法交换实现原理

    method_exchangeImplementations方法交换是用这个函数来实现的

    /**
      @param m1 : 方法一
      @param m2 : 方法二 
     */
    method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
    

    通过第一篇文章我们知道, Methodobjc_method结构体的指针, objc_method结构体中包含了SELIMP, 其中SEL是方法名称, IMP是方法的实现, 是指向函数体的函数指针, SELIMP是一一对应的, 而method_exchangeImplementations交换方法的原理 就是改变方法一方法二SELIMP的对应关系, 用一张图来表示
    交换前:

    屏幕快照 2018-08-19 下午9.59.37.png

    交换后:


    屏幕快照 2018-08-19 下午10.01.56.png

    通过图片, 相信你看的更明白了, 通过方法交换特性, 将SEL 1的方法实现IMP 1SEL 2的方法实现IMP 2 进行交换了, 也就是说, 当我给对象消息让它执行SEL 1 消息时, 实际上执行的是IMP 2

    原来如此, 那来看看代码吧

    代码

    接下来就通过代码, 为大家实现一个方法交换, 来交换一下UITableView-(void)reloadData方法, 当UITableView没有数据的时候, 显示一张没有更多数据的图片
    UITableView写一个category

    // .h
    #import <UIKit/UIKit.h>
    
    @interface UITableView (MethodSwizzling)
    
    @end
    
    // .m
    #import "UITableView+MethodSwizzling.h"
    #import <objc/runtime.h>
    
    @implementation UITableView (MethodSwizzling)
    //1. 在 load 类方法里进行方法交换
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            //2. 获取reloadData Method
            // class_getInstanceMethod函数, 获取对象方法
            // class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
            // 参数 cls: 获取方法对象的类
            // 参数 SEL: 获取的方法
            // 返回值:   返回 Method 用于 method_exchangeImplementations 进行方法交换
            Method reloadData = class_getInstanceMethod([self class], @selector(reloadData));
            //3. 获取要交换的my_reloadData 返回 Method 用于 方法交换
            Method my_reloadData = class_getInstanceMethod([self class], @selector(my_reloadData));
            //4. 方法交换
            method_exchangeImplementations(reloadData, my_reloadData);
        });
    }
    
    - (void)my_reloadData {
        // 由于方法已经交换, 此时进入[self my_reloadData];
        // 调用的是系统的 reloadData
        [self my_reloadData]; //5. 先更新数据
        //6. 如果当前tableView的cell行数为0时
        if (0 == self.visibleCells) {
        // 设置tableView的backgroundView为一张图片
            self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"无数据背景图"]];
        } else {
        // 否则tableView的backgroundView背景图置为nil
            self.backgroundView = nil;
        }
    }
    @end
    
    代码内函数讲解:

    class_getInstanceMethod函数, 获取对象方法的函数, 与之对应的还有一个
    class_getClassMethod函数, 用于获取类方法

    // 参数一: Class, 获取方法的类
    // 参数二: SEL,  方法名
    // 返回值: Method
    OBJC_EXPORT Method _Nullable
    class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
    
    // 参数一: Class, 获取方法的类
    // 参数二: SEL,  方法名
    // 返回值: Method
    OBJC_EXPORT Method _Nullable
    class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
    

    tableViewvisibleCells属性, 有的同学看它可能陌生

    // visibleCells 是一个只读属性, 返回的是tableView 当前cell的数组
    @property (nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;
    
    注意:

    方法交换应该在dispatch_once中完成, 由于方法交换改变了整个工程的状态, 所有的tableView调用reloadData方法都会进行交换, 确保在不同线程中也只执行一次

    tableView没有数据时的效果

    IMG_1516.PNG

    看到了吗? 利用Runtime的方法交换, 我们可以解决开发中很常见的tableView刷新没有数据的问题, 没想到吧, 这样的效果竟然可以用方法交换来实现, 其实方法交换能实现的功能还有很多, 需要我们细心去发掘, 今天就为大家讲解这一个应用场景, 通过上面的代码原理还有枯燥的概念, Runtime方法交换, 你学会了吗?

    相关文章

      网友评论

        本文标题:Runtime(三)方法交换

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