Runtime简称运行时,是一套底层的 C 语言 API,oc运行时最主要的就是消息机制。
对于C语言,在编译的时候会检查函数。决定调用哪个。
对于OC的函数,只有在真正运行的时候确定函数调用。OC作为动态语言,它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
类结构:
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA; // 继承自objc_object
Class superclass; //父类
cache_t cache; // 缓存
class_data_bits_t bits; // 方法列表、属性列表。协议列表。等等
}
cache_t 方法缓存

struct **cache_t** {
struct **bucket_t** *_buckets; // 一个散列表,[bucket_t,bucket_t],
mask_t _mask; //可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1
mask_t _occupied;//被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
需要注意的是:
1、类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份
2、从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。
3、类的方法缓存大小有没有限制?目前没有。。
4、为什么类的方法列表不直接做成散列表呢,做成list,还要单独缓存,多费事?
散列表是没有顺序的,Objective-C的方法列表是一个list,是有顺序的;Objective-C在查找方法的时候会顺着list依次寻找,并且category的方法在原始方法list的前面,需要先被找到,如果直接用hash存方法,方法的顺序就没法保证。
list的方法还保存了除了selector和imp之外其他很多属性
散列表是有空槽的,会浪费空间
class_data_bits_t
class_data_bits_t属于class_rw_t的封装
class_rw_t结构体又是对class_ro_t的封装,rw代表可读写、ro代表只读。
包括:
class_ro_t,//封装了类的基本原始信息成员变量,属性,方法和遵守协议等,格式为数组
protocols,
properties,
methods,
协议、属性、方法列表三者是二维数组; methods的元素是一个类或者它的分类的方法列表methodList,而methodList的元素是method_t。
method_t
{
SEL name:代表方法名称。
const char* types:返回值+参数+参数+…,不可变的字符类型指针。比如v@:,v代表返回值是void,@代表第一个参数是id类型的self,:代表第二个参数SEL。
IMP imp:无类型的函数指针,对应的是函数
}
总体结构引用一张图:

消息转发流程
调用查找阶段-转发阶段-动态解析
查找过程:
实例方法调用。首先根据isa指针找到它的类对象,查找类对象的缓存中是否有对应SEL的IMP方法实现,如果没有,则在当前类对象的实例方法列表,二分查找(有序情况)或遍历查找(无序情况)同名的方法实现IMP,如果找到,填充到缓存中,并返回selector,如果没有找到,根据类对象的superClass指针查找上一级缓存、方法列表。直到根类,如果没找到则进入消息转发流程。
类方法一样。只是调用地方不同。
消息转发
void resolveMethodTest(void){
NSLog(@"----------%s",__func__);
}
/**为一个类动态提供方法实现 实际还未进入方法转发
respondsToSelector:和instancesRespondToSelector:
也会调用resolveInstanceMethod:获取返回值
如果此方法返回NO则进入方法转发。。
一般我们不操作这个方法因为系统底层很多都需要它操作。返回yes。。*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s",__func__);
// if (sel == NSSelectorFromString(@"studentMethod")) {
// class_addMethod([self class], sel, resolveMethodTest, "v@:");
// return YES;
// //获取sayMaster方法的imp
// IMP imp = class_getMethodImplementation(self, @selector(resolveMethodTest));
// //获取sayMaster的实例方法
// Method sayMethod = class_getInstanceMethod(self, @selector(resolveMethodTest));
// //获取sayMaster的丰富签名
// const char *type = method_getTypeEncoding(sayMethod);
// //将sel的实现指向sayMaster
// return class_addMethod(self, sel, imp, type);
// }
return [super resolveInstanceMethod:sel];
}
/**允许你指定特有的对象。进行消息转发。从对象出发去查找消息*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == NSSelectorFromString(@"studentMethod")) {
// return Student.class; //调用classMethod
// return [[Student alloc] init]; //调用instanceMethod
return nil; //继续转发
}
return [super forwardingTargetForSelector:aSelector];
}
/**methodSignatureForSelector和forwardInvocation关联。。
提供方法签名调用。。
forwardinvocation可以更改target也可以更改方法*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == NSSelectorFromString(@"studentMethod")) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// avoidcrash内部采取catch方式拦截
@try {
if (anInvocation.selector == @selector(studentMethod)) {
anInvocation.target = Student.class;
anInvocation.selector = @selector(say);
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
} @catch (NSException *exception) {
} @finally {
}
}
其中# Type Encodings

- (void)methodName{}
”v@:“ —>
v是返回值void
@代表id方法的第一个参数self
:方法第二个参数sel
参考:
https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html
https://www.jianshu.com/p/c339d331e23e