前言
OC 原理探索:objc_msgSend 流程 中我们对方法的慢速查找流程进行了分析,如果方法最终找不到时,会将imp赋值为forward_imp然后返回,返回后有发生了什么呢,今天来继续探索。
准备工作
- objc4-818.2 源码
一、方法找不到的报错 底层原理
方法找不到的报错
在工程中添加如下代码:
@interface SSLStudent : SSLPerson
- (void)say1;
@end
@implementation SSLStudent
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLStudent *student = [SSLStudent alloc];
[student say1];
}
return 0;
}
复制代码
运行程序,可以看到下面的报错:

unrecognized selector sent to instance这个错误大家都遇到过,那么它是怎么发生的呢,接下来就探索一下它的底层原理。
报错的底层原理
通过 OC 原理探索:objc_msgSend 流程 我们可以知道方法找不到时,会返回forward_imp的值,forward_imp是通过_objc_msgForward_impcache来赋值的,如下:

我们来探索下_objc_msgForward_impcache的实现,全局搜索查找函数:

_objc_msgForward_impcache的实现只调用了__objc_msgForward一行代码。__objc_msgForward调用了TailCallFunctionPointer函数,TailCallFunctionPointer中调用了$0,$0也就是__objc_forward_handler函数,我们接下来查找__objc_forward_handler的实现。

OK!!,到这里就找到了unrecognized selector sent to instance报错的出处,字符串还拼接了+、-的方法符号。
二、对象方法动态决议
在报错之前有没有调用了什么函数呢,接下来断点调试源码,探索函数的调用。

-
第一次时,
behavior = 3 、LOOKUP_RESOLVER = 2,3&2 = 2,所以if表达式成立。
-
if表达式成立,会调用resolveMethod_locked函数。
resolveMethod_locked 解析

if (! cls->isMetaClass()),判断是否非元类。- 非
元类时,调用resolveInstanceMethod(inst, sel, cls)。 - 是
元类时,调用下面的代码:

- 最终通过
lookUpImpOrForwardTryCache(inst, sel, cls, behavior)返回,这个函数会对方法进行再次的查找。
lookUpImpOrForwardTryCache 解析
我们先来看lookUpImpOrForwardTryCache函数:

- 可以看到最终的
_lookUpImpTryCache函数,会进行cache_getImp快速查找和lookUpImpOrForward慢速查找。
resolveInstanceMethod 解析
我们来看一下resolveInstanceMethod(inst, sel, cls)函数的实现:

1. lookUpImpOrNilTryCache

- 这部分代码是对
resolveInstanceMethod:没有实现的容错处理,这里是不会进入的,系统提供了默认的实现。

2. objc_msgSend

- 通过
objc_msgSend给cls发送了一个resolveInstanceMethod:的消息,因为是给类发消息,所以是个+方法,在这个消息中我们可以添加相应代码解决报错。
3. lookUpImpOrNilTryCache
lookUpImpOrNilTryCache(inst, sel, cls)会进行方法的查找。

lookUpImpOrNilTryCache最终也是调用了_lookUpImpTryCache函数,会进行快速查找和慢速查找。
resolveInstanceMethod 容错处理
在SSLStudent添加下面的代码,并运行程序:
@implementation SSLStudent
- (void)ssl_say1
{
NSLog(@"ssl_say1");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(say1)) {
IMP imp = class_getMethodImplementation(self, @selector(ssl_say1));
Method method = class_getInstanceMethod(self, @selector(ssl_say1));
const char *type = method_getTypeEncoding(method);
class_addMethod(self, sel, imp, type);
}
return NO;
}
@end
复制代码

OK!!,ssl_say1成功打印,并且程序没有报错。
三、类方法的动态决议
在SSLStudent中添加say2类方法,不去实现,运行项目:
@interface SSLStudent : SSLPerson
- (void)say1;
+ (void)say2;
@end
复制代码

- 这次代码走到了
resolveClassMethod函数,接下里对它进行解析。
resolveClassMethod 解析
先看下resolveClassMethod的函数实现:

1. lookUpImpOrNilTryCache

-
这部分代码是对
resolveClassMethod:没有实现的容错处理,这里也是不会进入的,系统同样提供了默认的实现。
2. nonmeta

-
getMaybeUnrealizedNonMetaClass函数中只是做了一些元类的容错处理。 -
nonmeta最终取值是类不是元类
nonmeta的isa是0x0000000100008550,与cls的地址相同。
3. objc_msgSend

nonmeta是类,然后对类进行发送消息处理,也就是+方法,和resolveInstanceMethod时是一样的。
4. lookUpImpOrNilTryCache
lookUpImpOrNilTryCache函数,上边已经说过进行了方法的慢速查找和快速查找。
resolveClassMethod 容错处理
在SSLStudent添加下面的代码,并运行程序:
@implementation SSLStudent
+ (void)ssl_say2
{
NSLog(@"ssl_say2");
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(say2)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("SSLStudent"), @selector(ssl_say2));
Method method = class_getInstanceMethod(objc_getMetaClass("SSLStudent"), @selector(ssl_say2));
const char *type = method_getTypeEncoding(method);
class_addMethod(objc_getMetaClass("SSLStudent"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
@end
复制代码

没问题,ssl_say2成功打印,并且程序没有报错。
再次 resolveInstanceMethod

- 如果
resolveClassMethod没有完成方法的添加,resolveInstanceMethod还可以有添加方法的机会。
四、aop 和 oop 总结
NSObject 统一处理报错
创建NSObject+SSL类,运行程序:
#import <Foundation/Foundation.h>
@interface NSObject (SSL)
@end
#import "NSObject+SSL.h"
#import <objc/runtime.h>
@implementation NSObject (SSL)
- (void)say1{
NSLog(@"say1");
}
+ (void)say2{
NSLog(@"say2");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say1)) {
IMP say1 = class_getMethodImplementation(self, @selector(say1));
Method method = class_getInstanceMethod(self, @selector(say1));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, say1, type);
}else if (sel == @selector(say2)) {
IMP say2 = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(say2));
Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(say2));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("LGTeacher"), sel, say2, type);
}
return NO;
}
@end
复制代码

- 成功打印
say1、say2。
aop
我们项目中不可能把所有的报错方法全部hook,但是,我们可以只hook自己的方法,指定方法名为前缀_模块_具体这样。
比如说订单详情页的点击事件,方法定义为ssl_order_detailClick,如果发生了错误,可以执行跳转到订单列表页的操作,并进行错误的监控,这种方式叫做aop(面向切面编程)。
oop
与其相对的是oop(面向对象编程),它的特点是对象分工非常明确,但是会有很多的冗余代码。这个时候我们就会进行提取,提取会产生一个公共的类,所有人都会对它进行继承而产生强依赖,造成强耦合。
aop 的优缺点
所以我们应该无侵入、动态的注入代码,以方法和类为切点切入进去。
那它的缺点是什么呢,if、else太多,会造成性能的消耗。还有就是现在只是消息转发机制的前一个阶段,还没有进入转发流程,后面的流程就没有意义了,所以我们到转发流程再去做aop。
转发流程将在下一篇进行探索,点个赞支持一下吧!!???























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)