以下所有内容属笔者原创, 如有雷同纯属巧合, 未经允许不得转载.
OC中的方法调用实质是发送消息(objc_msgSend())
objc_msgSend()方法, 默认有2个必传参数:
- 接收者
- SEL选择器
objc_msgSend方法的底层, 苹果是用汇编语言实现的, 发送消息以后, 主要是查找方法, 查找方法的步骤是:
-
消息发送: 从实例对象对应的Class或者Meta Class中查找方法 -
动态方法解析: 如果步骤1没有找到方法, 会开始进行resolve, 动态解析. 在这个过程中, 如果开发者手动添加了方法实现, 那么会将方法添加到相应的Class或Meta Class中, 然后重新进行步骤1 -
消息转发: 如果步骤2执行结束之后, 也没有找到方法, 开始进行方法转发(___forwarding___ 方法), 若到此还没有查找到方法, 就会报错不识别方法选择器
一. 从实例对象对应的Class或者Meta Class中查找方法
实例对象对应的Class和Meta Class对象实际结构是一样的, 只是存储内容的差别而已
当实例对象或者类对象调用方法以后, 会将方法缓存到对应的Class或Meta Class中.
苹果特意在此添加了缓存, 提高查找速度. 并且, 不同于普通的for或while循环遍历, 苹果针对这片缓存区采用了散列算法, 通过空间换时间的方式, 来大大提高了查找效率.
-
缓存查找: 当发送消息以后, 马上从Class中开始查找方法, 并且优先从Cache中查找 -
方法列表查找: 若当前Class对应的Cache中找不到方法, 开始从Class对应的方法列表中查找 -
Super Class查找: 当前实例对象的Class或Meta Class中未查找到方法以后, 会通过Class或Meta Class的isa指针, 找到对应的父类, 继续进行上述2个步骤, 直至找到方法
二. 动态方法解析
若第一次无法正常从Cache和方法列表中查到方法, 则会进行动态方法解析. 一定注意, 这里说的是第一次.
动态解析:
- 若调用的是实例方法, 系统会调用
- (void)resolveInstanceMethod:(SEL)sel方法, 我们可以在这里, 对sel进行判断, 从而利用runtime的特性动态添加实例方法 - 若调用的是类方法, 系统会调用
- (void)resolveClassMethod:(SEL)sel方法, 同样可以在这里, 对sel进行判断, 利用runtime动态添加类方法 - 在
- (void)resolveInstanceMethod:(SEL)sel或- (void)resolveClassMethod:(SEL)sel方法中动态添加相应的实例方法或类方法之后, 这个方法实际上被会添加到对应的Class或Meta Class的方法列表中 - 此时, 系统会执行
retry操作, 第二次从Cache和Class方法列表中查找方法, 并缓存到Cache中. (注意这里说的第二次)
三. 消息转发
若经过动态方法解析以后, 都没有找到对应的方法, 那么系统会进行最后一步操作, 叫做消息转发
消息转发会先后调用以下几个方法:
-
forwardingTargetForSelector:返回值不为nil, 调用objc_msgSend(返回值, SEL); 返回值为nil, 执行下一步 -
methodSignatureForSelector:返回值不为nil, 调用forwardInvocation:方法; 返回值为nil, 执行下一步 doesNotRecognizeSelector: