什么是Runtime
- C语言是一门静态语言,在编译阶段已确定所有的数据类型,函数方法。
- Objective-C是一门动态语言,在编译时是不知道具体的变量类型,函数方式,是在运行阶段才确定相关类型,函数。因此我们可以动态去修改相关函数调用,变量等,使OC变得更灵活。
- Objective-C的运行时机制叫做Runtime。
- Runtime实际是一个库,OC通过Runtime去调用底层C语言方法。
消息转发机制
所有的Objective-C方法,在编译时都是转化为对C方法objc_msgsend()的调用
1.先来看看OC正常的函数调用
先创建一个Test类
// .h文件
@interface Test : NSObject
- (void)run;
- (void)eatFood:(NSString *)food;
+ (void)run;
@end
//.m文件
@implementation Person
- (void)run {
NSLog(@"Run方法运行");
}
- (void)eatFood:(NSString *)food {
NSLog(@"吃%@", food);
}
+ (void)run {
NSLog(@"执行了类方法Run");
}
@end
在viewController中调用Test.h,并在viewDidLoad执行
#import "Test.h"
- (void)viewDidLoad {
[super viewDidLoad];
Test *obj = [Test new];
//执行run方法
[obj run];
//执行eat方法
[obj eatFood:@"水果"];
//执行类方法
[Test run];
}
2.通过Runtime去调用方法
#import "Test.h"
//调用Runtime需要引入对应库
#import <objc/message.h>
- (void)viewDidLoad {
[super viewDidLoad];
Test *obj = [Test new];
objc_msgSend(obj, @selector(run));
objc_msgSend(obj, @selector(eatFood:), @"水果");
objc_msgSend([Test class], @selector(run));
}
objc_msgSend中可以传入多个参数
- 第一个参数为执行的对象(类方法,则传类,类实际也是一种对象)
- 第二个参数为调用的方法
-
第三个及后序参数为可选参数,传入第二个参数方法需要的参数
执行结果如图:
运行结果
2、注意苹果是禁止使用objc_msgsend方法的,要使用需要关闭对应检测Build Setting -> Enable Strict Checking of objc_msgSend Calls改为No
修改设置
交换方法
1.交换方法是我们开发中经常运用到,在很多老得项目中,有大量使用一个老方法,如果一个又一个去修改会耗费大量的时间,所以可以通过RunTime交换方法,快速替换。
2.举个例子
#在原有方法中调用了[NSURL URLWithString:]
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSLog(@"%@", url);
#如果url中不包含中文,该方法正常执行,但若插入中文,url会为空
url = [NSURL URLWithString:@"www.baidu.com\中文"];
NSLog(@"%@", url);

3.这时后就需要替换全部URLWithString方法。
我们可以创建一个NSURL的分类,实现一个新的方法NewWithString
@implementation NSURL (url)
+ (instancetype)NewWithString:(NSString *)str {
NSURL *url = [NSURL URLWithString:str];
if (!url) {
NSLog(@"URL为空");
}
return url;
}
@end
4.根据load方法在加载进入OC运行时被执行,可以在load方法中实现函数替换
- 我们需要用到class_getClassMethod获取到原有类方法和新的类方法(获取实例方法则通过class_getInstanceMethod)
- 通过method_exchangeImplementations来交换两个方法
+ (void)load {
//获取老方法
Method oldMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));
//获取新方法
Method newMethod = class_getClassMethod([NSURL class], @selector(NewWithString:));
//交换方法
method_exchangeImplementations(oldMethod, newMethod);
}
-
注意:
如果直接执行会陷入死循环。
因为在原有方法中,各个函数的方法实现如图(ps 图有点丑,见谅):
经过修改后变为:
因为我们在NewWithString执行:
NSURL *url = [NSURL URLWithString:str];
它会不断调用NewWithString的函数实现,导致死循环。需改成:
NSURL *url = [NSURL NewWithString:str];
执行结果如图:

方法懒加载
节约性能,我们经常用到属性的懒加载,函数方法同样可以懒加载
1.同样使用Test.h文件,不定义任何方法
2.我们在viewController直接调用
objc_msgSend(obj, @selector(lazyMethod));
因为没有定义方法,函数会直接报错。
3.实际上,在没有找到方法时,会执行对应类的
//对应类方法
+ (BOOL)resolveClassMethod:(SEL)sel;
//对应实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
4.我们先在Test.m中定义一个需要懒加载的方法
//id self, SEL _cmd为隐式参数可不写
int newMethod(id self, SEL _cmd) {
NSLog(@"执行了");
return 0;
}
5.在发现方法为需要懒加载的方法时,将函数加载进去
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"lazyMethod"]) {
class_addMethod([Test class], sel, newMethod, "i@:");
}
return [super resolveInstanceMethod:sel];
}
class_addMethod有四个参数
1.对应类
2.方法名
3.添加的方法
4.方法需要使用的参数,newMethod的返回值int 对应"i", id self对应"@",SEL对应":"具体对应可参考文档
执行结果如图:

KVO底层实现
kvo原理是创建对应类的子类,在子类中重写set方法,同时修改isa指针指向新创建的类。
1.创建一个Person类,并添加name属性用于监听测试。
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
2.创建NSObject分类,添加新的监听方法
@interface NSObject (KVO)
- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
3.在ViewController添加监听
- (void)viewDidLoad {
[super viewDidLoad];
_p = [Person new];
NSLog(@"修改前的类%@", [_p class]);
//使用自定义KVO监听
[self.p new_addObserver:self forKeyPath:@"name" options:0 context:nil];
NSLog(@"修改后的类%@", [_p class]);
}
//name改变后调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听到了name改变:%@",_p.name);
}
//点击改变值,触发KVO
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static int i = 0;
i++;
_p.name = [NSString stringWithFormat:@"%d", i];
}
4.NSObject分类实现
- (void)new_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
//self 被观察者
//observer 观察者
//1、自定义子类
//获取self类名
NSString *oldClassName = NSStringFromClass([self class]);
//创建self的子类
NSString *newClassName = [@"new_" stringByAppendingString:oldClassName];
const char *newName = [newClassName UTF8String];
//动态生成类
Class newClass = objc_allocateClassPair([self class], newName, 0);
//注册类 类加入内存中可供调用
objc_registerClassPair(newClass);
SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]]);
//2、添加set方法
class_addMethod(newClass, s, (IMP)setObject, "v:@:@");
//3、修改isa指针
object_setClass(self, newClass);
//保存观察者对象
objc_setAssociatedObject(self, "objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
void setObject(id self, SEL _cmd, id newName){
//1.调用super的set方法
id class = [self class];
//改变self的isa指针
object_setClass(self, class_getSuperclass(class));
// objc_msgSend(self, @selector(setName:), newName);
SEL s = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [objc_getAssociatedObject(self, "keyPath") capitalizedString]]);
objc_msgSend(self, s, newName);
NSLog(@"修改完毕");
//拿到观察者
id objc = objc_getAssociatedObject(self, "objc");
//通知观察者
objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:), objc_getAssociatedObject(self, "keyPath"),self, nil, nil);
//改回子类类型
object_setClass(self, class);
}
执行结果如图:




