类的结构
类分成了三部分

与我们调用方法相关的东西有三个,
objc_calss
中cache_t cache
方法缓存class_rw_t
中method_array_t methods
二维数组的方法列表class_ro_t
中method_list_t
一维数组的方法列表

cache_t cache
objc_calss
中 cache_t cache
,用散列表(哈希表)数组来缓存曾经调用过的方法,提高查找方法的速度。

cache_t
里面就三个成员,后两个是int类型,肯定不是存储的方法了,那就只有存储 _buckets
这个散列表中。bucket_t
就两个值,一个key一个imp,key的话就是SEL 方法名,而imp就是方法的内存地址。调用一个方法,到缓存里来找的时候,通过匹配比对方法名是不是一致。
找方法的具体实现,可以查看 runtime
源码中的,objc-cache.mm
文件。
method_array_t methods
class_rw_t
中 method_array_t methods
,包含原始类和分类的方法的二维数组,可读可写,动态添加方法的时候,是对这个二维数组进行的操作。

method_list_t
class_ro_t
中 method_list_t
,是只包含原始类的方法的数组,不包含分类的方法。只读不可写。

method_t
方法的数据结构如下
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
}
- name 方法的名字,可以叫做选择器。可以通过
@selector()
和sel_registerName()
获得。 - types 方法的编码,即返回值、参数的类型。由:返回值 参数1 参数2...参数N,格式编码拼接而成的字符串。
@encode
指令可以获取类型的编码字符。 - imp 方法的具体实现,是函数指针(函数的地址)
IMP定义

方法的调用
方法的调用顺序:系统是先从方法缓存中找有没有对应的方法,有的话就直接调用缓存里的方法,根据imp去调用方法,没有的话,就再去方法数组中遍历查找,找到后调用并保存到方法缓存里。
当我们调用一个方法是,其实底层是将这个方法转换成了 objc_msgSend
函数来进行调用,objc_msgSend
的执行流程可以分为3大阶段:
- 消息的发送
- 动态的方法解析
- 消息转发
消息的发送流程

动态的方法解析

+resolveInstanceMethod://添加对象方法 也就是-开头的方法
+resolveClassMethod://添加类方法 也就是+开头的方法
当我们需要动态方法解析式,需要实现这两个方法,返回 YES
。

class_getInstanceMethod
获取到一个 Method
的实现

class_addMethod
把方法添加到类对象或者元类对象 class_rw_t
中。
说明 : 动态添加方法,实质是找到某个方法的实现 imp,添加到类对象或者元类对象中,这个方法的是什么性质(类方法或者对象方法)无关紧要,重要的是 class_addMethod
的时候,希望这个方法成为对象方法,则添加到类对象的 class_rw_t
的方法中;希望它成为类方法,则添加到元类对象的 class_rw_t
的方法中。
消息转发
如果方法在消息发送阶段没有找到相关方法,也没有进行动态方法解析,这个时候就会走到消息转发阶段了。
所谓消息转发,就是你这个消息处理不了后可以找其他人或者其他方法来代替。

forwardingTargetForSelector
快速转发。或者通过 methodSignatureForSelector
和 forwardInvocation
进行标准转发。
- 通过
forwardingTargetForSelector
快速转发简单方便,缺点就是能做的事情有限,只能转发消息调用者
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
//return [[SomeClass alloc] init];
return [[NSObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
- 通过
methodSignatureForSelector
和forwardInvocation
进行标准转发,写起来麻烦点,需要写方法签名等信息,但是可以很大程度的自定义方法的转发,可以在找不到方法imp的时候做任何逻辑。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
//注意:这里返回的是我们要转发的方法的签名,比如我们现在是转发test方法 那就是返回的就是test方法的签名
//1.可以使用methodSignatureForSelector:方法从实例中请求实例方法签名,或者从类中请求类方法签名。
//2.也可以使用instanceMethodSignatureForSelector:方法从一个类中获取实例方法签名
//这里使用self的话会进入死循环 所以不可以使用 如果其他方法中有同名方法可以将self换成其他类
//return [self methodSignatureForSelector:aSelector];
//return [NSMethodSignature instanceMethodSignatureForSelector:aSelector];
//3.直接输入字符串
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//当返回方法签名后 就会转发到这个方法 所以我们可以在这里做想要实现的功能 可操作空间很大
//这个anInvocation里面有转发方法的信息,比如方法调用者/SEL/types/参数等等信息
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// SomeClass *obj = [[SomeClass alloc] init];
//指定target
// anInvocation.target = obj;
//对anInvocation做出修改后要执行invoke方法保存修改
// [anInvocation invoke];
//或者干脆一行代码搞定
// [anInvocation invokeWithTarget:[[SomeClass alloc] init]];
//转发到这里的话可操作性更大 也可以什么都不写
//相当于转发到的这个方法是个空方法 也不会报方法找不到的错误
//也可以在这里将报错信息提交给后台统计
//比如说某个方法找不到提交给后台 方便线上错误收集..等等
}
整体的流程图

方法交换
method swizzling
,开发中偶尔会用的,将自定义方法替换掉系统的方法,或者将两个方法交换。
- 利用
class_replaceMethod
替换方法的实现 - 利用
method_setImplementation
来直接设置某个方法的IMP - 利用
method_exchangeImplementations
交换两个方法的实现,交换的是Method
里的imp
。