本文介绍Runtime的基础知识
文章目录:
一、如何理解OC是动态语言,Runtime又是什么?
二、分析Runtime中的数据结构
三、深入理解Rutime消息发送原理
四、Runtime消息转发原理总结
一、如何理解OC是动态语言,Runtime又是什么?
源代码转换为可执行的程序,通常要经过三个步骤: 编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
**静态语言:**如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
**动态语言:**如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。
我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。
Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。
二、分析Runtime中的数据结构
- 数据结构分析
新建一个工程,工程中引入头文件:
#import <objc/runtime.h>
#import <objc/message.h>
“Command +鼠标点击”,进入Runtime的源码文件,接下来分析OC代码在C中对应的结构,看看我们平时写oc代码时创建的类、对象、方法、协议在Runtime中是怎样的存在。
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; // objc_class 结构体的实例指针
#if !__OBJC2__
Class _Nullable super_class // 指向父类的指针
const char * _Nonnull name // 类的名字
long version // 类的版本信息,默认为 0
long info // 类的信息,供运行期使用的一些位标识
long instance_size // 该类的实例变量大小;
struct objc_ivar_list * _Nullable ivars // 该类的实例变量列表
struct objc_method_list * _Nullable * _Nullable methodLists // 方法定义的列表
struct objc_cache * _Nonnull cache // 方法缓存
struct objc_protocol_list * _Nullable protocols // 遵守的协议列表
#endif
先大概浏览一下Runtime的.h文件中的结构和方法,再具体分析都有什么作用
2. Runtime中的概念解析
2.1 objc_msgSend
所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。objc_msgSend(receiver,selector); 是 [receiver selector]; 对应的 C 函数。
2.2 Class(类)
objc_class 结构体 定义了很多变量:自身的所有实例变量(ivars)、所有方法定义(methodLists)、遵守的协议列表(protocols)等。objc_class 结构体 存放的数据称为 元数据(metadata)。
objc_class 结构体 的第一个成员变量是 isa 指针,isa 指针 保存的是所属类的结构体的实例的指针,这里保存的就是 objc_class 结构体的实例指针,而实例换个名字就是 对象。换句话说,Class(类) 的本质其实就是一个对象,我们称之为 类对象。
2.3 Object(对象)
这里的 id 被定义为一个指向 objc_object 结构体 的指针。从中可以看出 objc_object 结构体 只包含一个 Class 类型的 isa 指针。
换句话说,一个 Object(对象)唯一保存的就是它所属 Class(类) 的地址。当我们对一个对象,进行方法调用时,比如 [receiver selector];,它会通过 objc_object 结构体的 isa 指针 去找对应的 objc_class 结构体,然后在 objc_class 结构体 的 methodLists(方法列表) 中找到我们调用的方法,然后执行。
2.4 Meta Class(元类)
对象(objc_object 结构体) 的 isa 指针 指向的是对应的 类对象(objc_class 结构体)。那么 类对象(objc_class 结构体)的 isa 指针 又指向什么呢?
objc_class 结构体 的 isa 指针 实际上指向的的是 类对象 自身的 Meta Class(元类)。
那么什么是 Meta Class(元类)?
Meta Class(元类) 就是一个类对象所属的 类。一个对象所属的类叫做 类对象,而一个类对象所属的类就叫做 元类。
2.5 方法(Method)
struct objc_method {
SEL _Nonnull method_name //方法的名字
char * _Nullable method_types //参数的类型
IMP _Nonnull method_imp //就是函数的地址
}
三、深入理解Rutime消息发送原理
1、消息转发的基本原理
Objective-C 语言 中,对象方法调用都是类似 [receiver selector];
的形式,其本质就是让对象在运行时发送消息的过程。
我们来看看方法调用 [receiver selector];
在『编译阶段』和『运行阶段』分别做了什么?
编译阶段:[receiver selector]
; 方法被编译器转换为:
objc_msgSend(receiver,selector) (不带参数)
objc_msgSend(recevier,selector,org1,org2,…)(带参数)
运行时阶段:消息接受者 recever 寻找对应的 selector。
通过 recevier 的 isa 指针 找到 recevier 的 Class(类);
在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);
如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;
如果在 Class(类) 中没有找到这个 selector,就继续在它的 superClass(父类)中寻找;
一旦找到对应的 selector,直接执行 recever 对应 selector 方法实现的 IMP(方法实现)。
若找不到对应的 selector,消息被转发或者临时向 recever 添加这个 selector 对应的实现方法,否则就会发生崩溃。
2、消息转发
2.1消息动态解析
举例:创建个MessageSend类,声明一个方法,.m文件不写实现,然后调用这个方法,
@interface MessageSend : NSObject
- (void)sendMessage:(NSString *)msg;
@end
毫无意外的crash了,报错
2018-08-03 23:21:20.950576+0800 RunTime[844:370505] -[MessageSend sendMessage:]: unrecognized selector sent to instance 0x282bf0ce0
下面动态添加方法,即调用另一个方法
先添加一个方法
void dynamicMethodIMP(id self, SEL _cmd, NSString *msg) {
NSLog(@"%@转发到这里----%@====%@", self, NSStringFromSelector(_cmd), msg);
}
然后重写系统的resolveClassMethod:(SEL)
或者resolveInstanceMethod:(SEL)
方法,我们声明的属性方法,所以重写这个:
+ (BOOL)resolveInstanceMethod:(SEL)sel {//实例
/*@param 给哪个类添加方法
*@param sel 方法列表中的名字,方法编号
*@param imp 方法实现
*@param 参数
* @return 如果添加方法成功返回 YES,否则返回 NO
*/
//BOOL class_addMethod(Class cls, SEL name, IMP imp, const char * _Nullable types);
if (sel == @selector(receiveMessage:)) {
return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
}
//也可以通过方法名进行转发
// NSString *msgName = NSStringFromSelector(sel);
// if ([msgName isEqualToString:@"sendMessage:"]) {
// return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
// }
return [super resolveInstanceMethod:sel];;
}
具体的参数类型可查查看官方文档:传送门
运行结果:
2018-08-03 23:30:19.783188+0800 RunTime[851:371928] <MessageSend: 0x280c30720>转发到这里----receiveMessage:====message
可以看到程序运行并且成功调用了dynamicMethodIMP
方法,说明消息转发成功。
2.2消息接受者重定向
如果上一步中 +resolveInstanceMethod:
或者 +resolveClassMethod:
没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector:
,Runtime 就会调用这个方法,允许我们将消息的接受者转发给其他对象。
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]) {
return [[PersonMessage alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
PersonMessage.m中
- (void)sendMessage:(NSString *)msg{
NSLog(@"PersonMessage");
}
结果:
2018-08-04 00:43:37.241994+0800 RunTime[909:382058] PersonMessage
2.3消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 methodSignatureForSelector:
方法获取函数的参数和返回值类型。
如果 methodSignatureForSelector:
返回了一个 NSMethodSignature
对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 forwardInvocation:
消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 -methodSignatureForSelector:
返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector:
消息,程序也就崩溃了。
/*消息重定向*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 获取函数的参数和返回值类型,返回签名
if ([NSStringFromSelector(aSelector) isEqualToString:@"sendMessage:"]) {
//调用forwardInvocation:,如果返回nil调用doesNotRecognizeSelector:
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector; // 从 anInvocation 中获取消息
PersonMessage *p = [[PersonMessage alloc] init];
if([p respondsToSelector:sel]) { // 判断 Person 对象方法是否可以响应 sel
[anInvocation invokeWithTarget:p]; // 若可以响应,则将消息转发给其他对象处理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然无法响应,则报错:找不到响应方法
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"doesNotRecognizeSelector");
}
- 消息发送以及转发机制总结
调用[receiver selector];
后,进行的流程:
编译阶段:[receiver selector]
; 方法被编译器转换为:
objc_msgSend(receiver,selector)
(不带参数)
objc_msgSend(recevier,selector,org1,org2,…)
(带参数)
运行时阶段:消息接受者 recever
寻找对应的 selector
。
通过 recevier
的 isa 指针 找到 recevier
的 class
(类);
在 Class(类) 的 cache
(方法缓存) 的散列表中寻找对应的 IMP
(方法实现);
如果在 cache
(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list
(方法列表) 中找对应的 selector
,如果找到,填充到 cache
(方法缓存) 中,并返回 selector
;
如果在 class(类) 中没有找到这个 selector
,就继续在它的 superclass
(父类)中寻找;
一旦找到对应的 selector
,直接执行 recever
对应 selector
方法实现的 IMP(方法实现)。
若找不到对应的 selector
,Runtime 系统进入消息转发机制。
运行时消息转发阶段:
动态解析:通过重写 +resolveInstanceMethod:
或者 +resolveClassMethod:
方法,利用 class_addMethod
方法添加其他函数实现;
消息接受者重定向:如果上一步添加其他函数实现,可在当前对象中利用 -forwardingTargetForSelector:
方法将消息的接受者转发给其他对象;
消息重定向:如果上一步没有返回值为 nil
,则利用 -methodSignatureForSelector:
方法获取函数的参数和返回值类型。
如果 -methodSignatureForSelector:
返回了一个 NSMethodSignature
对象(函数签名),Runtime 系统就会创建一个 NSInvocation
对象,并通过 -forwardInvocation:
消息通知当前对象,给予此次消息发送最后一次寻找 IMP
的机会。
如果 -methodSignatureForSelector:
返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector:
消息,程序也就崩溃了。