什么是RunTime?
字面意思运行时,之前玩过JAVA 有点像多态的意思,那么在OC里面RunTime到底是什么?这要结合OC的语言特性来分析,从我们的源码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行
C语言作为一门静态语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好要调用的函数,以及函数实现。
而OC 语言是一门动态语言,在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。
OC 语言 把一些决定性的工作从编译阶段、链接阶段推迟到了运行时阶段,这样让OC语言变得更加灵活。我们甚至可以在运行的时候,动态的去修改一个方法的实现。这里其实也 逆向开发提供了更大空间。
而实现 OC 语言 运行时机制 的一切基础就是 Runtime,下面简单讲下
-
消息发送机制(Object实例对象 --isa--》Class类对象 --isa--》Meta Class)
- Object对象 ()
- Class类
- Meta Class (元类)
-
转发机制
当一个方法找不到的时候,Runtime 提供了 消息动态解析、消息接受者重定向、消息重定向 等三步处理消息- 消息动态解析
Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。我们可以通过重写这两个方法,添加其他函数实现,并返回 YES, 那运行时系统就会重新启动一次消息发送的过程
- 消息动态解析
#import "ViewController.h"
#include "objc/runtime.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 函数
[self performSelector:@selector(fun)];
}
// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(fun)) { // 如果是执行 fun 函数,就动态解析,指定新的 IMP
class_addMethod([self class], sel, (IMP)funMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void funMethod(id obj, SEL _cmd) {
NSLog(@"funMethod"); //新的 fun 函数
}
@end
- 消息接受者重定向
如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向
#import "ViewController.h"
#include "objc/runtime.h"
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 方法
[self performSelector:@selector(fun)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 为了进行下一步 消息接受者重定向
}
// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(fun)) {
return [[Person alloc] init];
// 返回 Person 对象,让 Person 对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
- 消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 方法获取函数的参数和返回值类型。
如果 -methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 -forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。
所以我们可以在 -forwardInvocation: 方法中对消息进行转发
#import "ViewController.h"
#include "objc/runtime.h"
@interface Person : NSObject
- (void)fun;
@end
@implementation Person
- (void)fun {
NSLog(@"fun");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 执行 fun 函数
[self performSelector:@selector(fun)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 为了进行下一步 消息接受者重定向
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 为了进行下一步 消息重定向
}
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector; // 从 anInvocation 中获取消息
Person *p = [[Person alloc] init];
if([p respondsToSelector:sel]) { // 判断 Person 对象方法是否可以响应 sel
[anInvocation invokeWithTarget:p]; // 若可以响应,则将消息转发给其他对象处理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然无法响应,则报错:找不到响应方法
}
}
@end
- 方法交换 Method Swizzling
Method Swizzling 用于改变一个已经存在的 selector 实现。我们可以在程序运行时,通过改变 selector 所在 Class(类)的 method list(方法列表)的映射从而改变方法的调用。其实质就是交换两个方法的 IMP(方法实现
注意事项:
- 1、应该只在 +load 中执行 Method Swizzling
- 2、Method Swizzling 在 +load 中执行时,不要调用 [super load];
- 3、Method Swizzling 应该总是在 dispatch_once 中执行
- 4、使用 Method Swizzling 后要记得调用原生方法的实现
使用场景
- 1、页面访问统计
#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
if (![self isKindOfClass:[UIViewController class]]) { // 剔除系统 UIViewController
// 添加统计代码
NSLog(@"进入页面:%@", [self class]);
}
[self xxx_viewWillAppear:animated];
}
@end
- 2 根据屏幕大小适配字体 (代码布局)
#import "UIFont+AdjustSwizzling.h"
#import <objc/runtime.h>
#define XXX_UISCREEN_WIDTH 375
@implementation UIFont (AdjustSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(systemFontOfSize:);
SEL swizzledSelector = @selector(xxx_systemFontOfSize:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
+ (UIFont *)xxx_systemFontOfSize:(CGFloat)fontSize {
UIFont *newFont = nil;
newFont = [UIFont xxx_systemFontOfSize:fontSize * [UIScreen mainScreen].bounds.size.width / XXX_UISCREEN_WIDTH];
return newFont;
}
@end
- 3、 处理按钮重复点击
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>
@interface UIButton()
// 重复点击间隔
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
@end
@implementation UIButton (DelaySwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(sendAction:to:forEvent:);
SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 如果想要设置统一的间隔时间,可以在此处加上以下几句
if (self.xxx_acceptEventInterval <= 0) {
// 如果没有自定义时间间隔,则默认为 0.4 秒
self.xxx_acceptEventInterval = 0.4;
}
// 是否小于设定的时间间隔
BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
// 更新上一次点击时间戳
if (self.xxx_acceptEventInterval > 0) {
self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
}
// 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件
if (needSendAction) {
[self xxx_sendAction:action to:target forEvent:event];
}
}
- (NSTimeInterval )xxx_acceptEventInterval{
return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue];
}
- (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval )xxx_acceptEventTime{
return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue];
}
- (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 4、tableView、CollectionView 异常时加载提示图片
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UITableView (ReloadDataSwizzling)
@property (nonatomic, assign) BOOL firstReload;
@property (nonatomic, strong) UIView *placeholderView;
@property (nonatomic, copy) void(^reloadBlock)(void);
@end
/*--------------------------------------*/
#import "UITableView+ReloadDataSwizzling.h"
#import "XXXPlaceholderView.h"
#import <objc/runtime.h>
@implementation UITableView (ReloadDataSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(reloadData);
SEL swizzledSelector = @selector(xxx_reloadData);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xxx_reloadData {
if (!self.firstReload) {
[self checkEmpty];
}
self.firstReload = NO;
[self xxx_reloadData];
}
- (void)checkEmpty {
BOOL isEmpty = YES; // 判空 flag 标示
id <UITableViewDataSource> dataSource = self.dataSource;
NSInteger sections = 1; // 默认TableView 只有一组
if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
sections = [dataSource numberOfSectionsInTableView:self] - 1; // 获取当前TableView 组数
}
for (NSInteger i = 0; i <= sections; i++) {
NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i]; // 获取当前TableView各组行数
if (rows) {
isEmpty = NO; // 若行数存在,不为空
}
}
if (isEmpty) { // 若为空,加载占位图
if (!self.placeholderView) { // 若未自定义,加载默认占位图
[self makeDefaultPlaceholderView];
}
self.placeholderView.hidden = NO;
[self addSubview:self.placeholderView];
} else { // 不为空,隐藏占位图
self.placeholderView.hidden = YES;
}
}
- (void)makeDefaultPlaceholderView {
self.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
XXXPlaceholderView *placeholderView = [[XXXPlaceholderView alloc] initWithFrame:self.bounds];
__weak typeof(self) weakSelf = self;
[placeholderView setReloadClickBlock:^{
if (weakSelf.reloadBlock) {
weakSelf.reloadBlock();
}
}];
self.placeholderView = placeholderView;
}
- (BOOL)firstReload {
return [objc_getAssociatedObject(self, @selector(firstReload)) boolValue];
}
- (void)setFirstReload:(BOOL)firstReload {
objc_setAssociatedObject(self, @selector(firstReload), @(firstReload), OBJC_ASSOCIATION_ASSIGN);
}
- (UIView *)placeholderView {
return objc_getAssociatedObject(self, @selector(placeholderView));
}
- (void)setPlaceholderView:(UIView *)placeholderView {
objc_setAssociatedObject(self, @selector(placeholderView), placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void (^)(void))reloadBlock {
return objc_getAssociatedObject(self, @selector(reloadBlock));
}
- (void)setReloadBlock:(void (^)(void))reloadBlock {
objc_setAssociatedObject(self, @selector(reloadBlock), reloadBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
- 5、KVO KVC APM(应用性能管理、防止程序崩溃)