Runtime(三)方法交换
在刚开始关注Runtime
时, 不知道小伙伴们是否听过一种传说
Runtime
是Objective-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)
通过第一篇文章我们知道, Method
是objc_method
结构体的指针, objc_method
结构体中包含了SEL
和IMP
, 其中SEL
是方法名称, IMP
是方法的实现, 是指向函数体的函数指针, SEL
和IMP
是一一对应的, 而method_exchangeImplementations
交换方法的原理 就是改变方法一
和方法二
中SEL
和IMP
的对应关系, 用一张图来表示
交换前:
交换后:
屏幕快照 2018-08-19 下午10.01.56.png
通过图片, 相信你看的更明白了, 通过方法交换
特性, 将SEL 1
的方法实现IMP 1
和 SEL 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)
tableView
的visibleCells
属性, 有的同学看它可能陌生
// visibleCells 是一个只读属性, 返回的是tableView 当前cell的数组
@property (nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;
注意:
方法交换
应该在dispatch_once
中完成, 由于方法交换
改变了整个工程的状态, 所有的tableView
调用reloadData
方法都会进行交换, 确保在不同线程中也只执行一次
当tableView
没有数据时的效果
看到了吗? 利用Runtime
的方法交换, 我们可以解决开发中很常见的tableView
刷新没有数据的问题, 没想到吧, 这样的效果竟然可以用方法交换
来实现, 其实方法交换
能实现的功能还有很多, 需要我们细心去发掘, 今天就为大家讲解这一个应用场景, 通过上面的代码
和原理
还有枯燥的概念
, Runtime
的方法交换
, 你学会了吗?
网友评论