0%

Runtime是一个以C和汇编语言编写的库,它是OC面向对象以及动态机制的基石
OC是一门动态化语言,其动态能力包括在运行时创建类和对象、进行消息传递、消息转发。
机器只能识别汇编语言,高级编程语言想要编译成可执行文件需要先转编程汇编,OC是无法直接编译成汇编语言的,需要由C语言进行过渡。我们知道OC是一门面向对象的语言,而C语言更多的是面向过程,所以需要将面向对象的类转变成面向过程的结构体。而实际上OC就是这么做的,我们观察类的源码可以发现它的内部是由许多个结构体组成的。

消息传递

消息传递方法:msg_send(id self, SEL op, …)。例如一个对象的方法:[person eat],运行时编译器转成消息发送msg_send(person, eat),过程是这样的:

  1. 通过person的isa指针找到它的class
  2. 在它内部的method_list中查找eat方法(类内部有一个cache用于存储调用过的方法,一个类可能会有非常多的方法,而常用的可能就是其中的10%-20%,如果每次都去遍历查找method_list效率太低,所以会对常用的方法进行缓存,以方法名为key,方法的IMP为value,先在缓存中进行查找,若没找到再去method_list中找,这样极大地提升了查找效率)
  3. 若在class的method_list以及cache中都没有找到,则去superClass中查找,一层层往上查找,直到找到eat为止
  4. 找到eat后,去执行它对应的IMP

消息转发

上述消息传递到第4部之后,若没有找到对应的selector,runtime就会开始消息转发,一共有三次机会

  1. 动态方法解析
  2. 备用接收者
  3. 完整消息转发

动态方法解析

若没有找到方法,会触发+ (BOOL)resolveInstanceMethod:(SEL)sel+ (BOOL)resolveClassMethod:(SEL)sel方法,在该方法实现中你有机会提供一个方法(函数实现),如果你添加了函数并返回了YES,则运行时系统会重新启动一次消息发送的过程,代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.testClass.name = @“Lucifer”;
[self performSelector:@selector(runtimeTest)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@“resolveInstanceMethod”);
if (sel == NSSelectorFromString(@“runtimeTest”)) {
class_addMethod([self class], sel, (IMP)exchangeMethod, “v@:”);
}
return [super resolveInstanceMethod:sel];
}

void exchangeMethod(id obj, SEL _cmd){
NSLog(@“runtimeTest”);
}

如果resolve返回NO,运行时会进入到下一步:- (id)forwardingTargetForSelector:(SEL)aSelector,可以在这一部进行消息接收者替换

备用接收者

如果对象实现了- (id)forwardingTargetForSelector:(SEL)aSelector,运行时就会给你机会启用一个对象做为备用接收者,将消息转发给它进行处理
实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "TestClass.h"
@implementation TestClass
- (void)runtimeTest {
NSLog(@"TestClass runtimeTest");
}
@end

#import "ViewController.h"
#import "TestClass.h"
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(runtimeTest)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == NSSelectorFromString(@"runtimeTest")) {
return [TestClass new]; //消息转发给TestClass对象,让textClass对消息进行处理,实现runtimeTest方法
}
return [super forwardingTargetForSelector:aSelector];
}
@end

完整消息转发

如果以上两步还不能处理未知消息,则会启用完整消息转发,这里说的完整消息,就是在触发- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法时获取到函数的参数和返回值类型,如果- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回 的是nil,runtime会发出doesNotRecognizeSelector:消息,这时程序崩溃。如果返回了一个函数签名(签名里包含了对象、方法名、方法返回值类型),runtime会创建一个NSInvovation对象,并调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,可以在这个方法里进行完整消息转发。代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "TestClass.h"
@implementation TestClass
- (void)runtimeTest {
NSLog(@"TestClass runtimeTest");
}
@end

#import "ViewController.h"
#import "TestClass.h"
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(runtimeTest)];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == NSSelectorFromString(@"runtimeTest")) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];// v@:是苹果官方声明的签名 return value @:target ::selector,这里v对应void,@对应self,:对应runtimeTest
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
TestClass *tClass = [[TestClass alloc] init];
if ([tClass respondsToSelector:sel]) {
[anInvocation invokeWithTarget:tClass]; //调用tClass去实现selector
}else{
[self doesNotRecognizeSelector:sel];
}
}
@end

runtime应用

  • 关联对象,给category添加属性
1
2
3
4
5
6
7
8
static char KAgeKey;
- (void)setAge:(NSString *)age{
objc_setAssociatedObject(self, &KAgeKey, age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)age{
return objc_getAssociatedObject(self, &KAgeKey);
}

  • 方法替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@implementation ViewController

+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originSel = @selector(viewDidLoad);
SEL swizzledSel = @selector(newViewDidLoad);

Method originalMethod = class_getInstanceMethod(class, originSel);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSel);

BOOL isAdded = class_addMethod(class, originSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (isAdded) {
class_replaceMethod(class, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (void)viewDidLoad {
NSLog(@"viewDidLoad");
[super viewDidLoad];
}

- (void)newViewDidLoad{
NSLog(@"newViewDidLoad");
[self newViewDidLoad];
}

  • KVO实现
    当我们对一个对象A的name属性进行观察时,runtime会动态创建一个A的子类NSKVONotifying_A,并在NSKVONotifying_A中实现name的setter方法,在setter方法中观察name值的变化,并发出通知。那么是如何发出通知的呢,其实是在setter内部添加了willChangeValueForKey:和didChangeValueForKey:方法,代码如下:
1
2
3
4
5
6
- (void)setName:(NSString *)newName { 
[self willChangeValueForKey:@"name"]; //KVO 在调用存取方法之前总调用
[super setValue:newName forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO 在调用存取方法之后总调用
}

  • 消息转发实现热更新(JSPatch)
  • 实现字典转模型(MJExtension实现原理)
    通过runtime提供的class_copyPropertyList方法遍历自身素有属性,然后用property_getName、property_getAttributes方法拿到对应属性名和属性类型,最后与json数据进行比对,对model进行赋值。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即释放properties指向的内存
free(properties);

//(2)根据类型给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}

[super message]的底层实现

objc_msgSendSuper({self, [Person class]}, @selector(message))

  1. 消息接受者仍然是子类对象
  2. 从父类开始查找方法实现