日常小知识点日后可能用到的第三方

Runtime(三)方法交换

2018-08-19  本文已影响23人  2d9cba783f86

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方法交换, 你学会了吗?

上一篇 下一篇

猜你喜欢

热点阅读