通过这篇文章可以获得什么:
- 为什么可以探索动态消息转发?
- 控制台打印为什么会提示unrecognized selector sent to instance?
- 控制台打印查找流程图
- 动态方法决议
- 位运算实现单例解读
- resolveMethod_locked相关源码解读
- 实例方法的动态决议
- 类方法的动态决议
- 整合实例对象/类对象的动态方法决议
- oop与aop简单说明
探索消息转发的起因
- 我在
FFPerson
类里面创建了一个实例方法likeGirls
,但是并未实现 - 然后我在
main.m
文件中初始化FFPerson
类,并调用likeGirls
方法 - 出现经典错误:
'-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
,无法找到该方法的实例,即未实现
。
案例代码
@interface FFPerson : NSObject
//声明了一个实例方法
- (void)likeGirls;
@end
@implementation FFPerson
//该实例方法并没有实现
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//初始化FFPerson的实例对象
FFPerson *person = [FFPerson alloc];
//调用likeGirls实例方法
[person likeGirls];
}
return 0;
}
复制代码
控制台经典报错:
2021-07-04 09:58:10.002598+0800 001-探索消息转发的起因[12184:1009168] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0
2021-07-04 09:58:10.017465+0800 001-探索消息转发的起因[12184:1009168] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff205a56af __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff202dd3c9 objc_exception_throw + 48
2 CoreFoundation 0x00007fff20627c85 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2050d07d ___forwarding___ + 1467
4 CoreFoundation 0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
terminating with uncaught exception of type NSException
(lldb)
复制代码
截图补充:
疑问点一:当方法未实现的时候为什么会报unrecognized selector sent to instance
这个错误呢?
通过前几次文章的分享,知道了一个消息的查找流程:
快速查找流程(已完成探索)
->慢速查找流程(已完成探索)
->动态方法转发流程(本篇探索内容)
当消息进入到慢速查找流程的时候最终会停留在LookUpImpOrForward
这个核心方法上,那么我此篇内容依然接续着慢速查找流程无法匹配
的情况看LookUpImpOrForword的后续操作,所以入手点
就在这个方法,全局搜索:
objc4源码解答(截取关键源码):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
}
return imp;
}
复制代码
通过LookUpImpOrForward
这个方法可以得知,imp
在未找到的时候会被默认赋值一个forward_imp,forward_imp
只是一个符号,它代表的是(IMP)_objc_msgForward_impcache
,接下来去全局搜索(IMP)_objc_msgForward_impcache
:
继续全局查找__objc_msgForward
,我这里还是只关心arm64
的架构:
全局搜索__objc_forward_handler
,发现没有我想要的结果,没有找到具体的实现
地方
将__objc_forward_handler
的下划线“_”
删除一个,再次搜索_objc_forward_handler
到这里,守得云开见月明
,看到了控制台打印为什么会提示unrecognized selector sent to instance?
探索流程图:
总结:
由控制台打印信息
unrecognized selector sent to instance
为出发点,据悉向下深挖方法的响应流程
通过对
LookUpImpOrForward
的分析得出在慢速查找未找到
的时候,给imp
默认赋值forward_imp
,即objc_msgForward_impcache
通过对
objc_msgForward_impcache
的查找,发现在汇编
中它只是一个中间函数
,真正指向的是objc_msgForward
对
objc_msgForward
的分析得出,方法内调用了objc_forward_handle
获取返回值
存到x17
寄存器,再由TailCallFunctionPointe
r方法跳转到x17
寄存器内真正指向的imp
地址
_objc_forward_handler
会给定默认
的实现objc_defaultForwardHandle
,当imp
最终的查找流程全部走完
的时候,还未找到
imp,那么此时就会进入objc_defaultForwardHandle
函数,将错误信息
打印出来。
动态方法决议
当上述一系列的方法都走完之后,无法找到sel
对应的imp
,这个是后循环查找结束了,接下来进入动态方法决议
objc4源码解答(截取关键源码):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
//找到了sel对应的imp
if (fastpath(imp)) {
goto done;
}
}
/**
* 如果遍历查找的过程找到了,会跳过此步骤,取到done分支,进行后续操作
* 如果找不到,会进行下面这个算法,最终进入resolveMethod_locked函数
* 此算法真正达到的目的为单例,保证一个lookUpImpOrForward
* 只执行一次resolveMethod_locked
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
return imp;
}
复制代码
位运算实现单例解读
操作符号解读:
&
:按位与,比较的是二进制,相同位置数字相同为1,不同为0^
:按位或,比较的是二进制,相同位置数字不同为1,相同为0^=
:按位或,比较的是二进制,相同位置数字不同为1,相同为0,将结果赋值为运算符左边。
前提条件(初始值)
behavior = 3
LOOKUP_RESOLVER = 2
初次判断操作
操作一:behavior & LOOKUP_RESOLVER
= 3 & 2 = 0x11 & 0x10 = 0x10 = 2
重置behaivor
操作二:behavior
= behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 0x11 ^ 0x10 = 0x01 = 1
再次进入判断操作(第二次至无限次)
操作三:behavior & LOOKUP_RESOLVER
= 1 & 2 = 0x01 & 0x10 = 0x00 = 0
结论
保证了每一个lookUpImpOrForward
函数最多只能执行一次resolveMethod_locked
(动态方法决议),直到behavior
被重新赋值
resolveMethod_locked
此方法存在的意义:当你调用了一个方法的时候,第一进入消息的快速查询流程
-> 然后进入消息的慢速查找流程
,当底层源码已经给你方法查找了2遍
之后依然找不到你实现的地方,此时imp=nil
,理论上来讲程序应该崩溃
,但是在开发者
的角度上来讲,此做法会令这个框架不稳定
,或者说这个系统很不友善
。所以此框架决定再给你一次机会
,让你重新拯救地球
,为你提供了一个自定义的imp
返回的机会,resolveMethod_locked此函数就是动态消息转发的入口
。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
//imp为实例方法
resolveInstanceMethod(inst, sel, cls);
}
else {
//imp为类方法
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// 经过resolveInstanceMethod函数很有可能已经对sel对应imp完成了动态添加
// 所以再一次尝试查找
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
此函数里面有三个关键的函数:
-
resolveInstanceMethod
:实例方法动态添加imp
-
resolveClassMethod
:类方法动态添加imp
-
lookUpImpOrForwardTryCache
,当完成添加之后,回到之前的慢速查找
流程再来一遍
resolveInstanceMethod
/*********************************************************
* 解析实例方法
* 调用+resolveInstanceMethod,寻找要添加到类cls的方法。
* cls 可能是元类或非元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//当你为实现resolveInstanceMethod的时候,此处也不会进入return
//因为系统给resolveInstanceMethod函数默认返回NO,即默认实现了
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
return;
}
//系统会在此处为你发送一个消息resolve_sel
//当你的这个类检测了这个消息,并且做了处理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
resolveClassMethod
/*********************************************************
* 解析类方法
* 调用+resolveClass 方法,寻找要添加到类cls 的方法。
* cls 应该是一个元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//当你为实现resolveClassMethod的时候,此处也不会进入return
//因为系统给resolveClassMethod函数默认返回NO,即默认实现了
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//nonmeta容错处理
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//系统会在此处为你发送一个消息resolveClassMethod
//当你的这个类检测了这个消息,并且做了处理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
resolveClassMethod & resolveInstanceMethod
//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
复制代码
lookUpImpOrForwardTryCache
过度函数
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
复制代码
_lookUpImpTryCache
通过动态添加方法之后,再次尝试查找sel对应的最新添加的imp
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//当类未初始化的时候,进入lookUpImpOrForward
//在里面处理不缓存任何方法
if (slowpath(!cls->isInitialized())) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//通过汇编查找
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
//共享缓存查找
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
//如果依然找不到,证明方法真的不存在,也就是说,只能依靠方法的动态添加了
//那么此时再次进入方法的慢速查找流程
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//此判断是当前imp已经存在了,并且这个imp是默认赋值的forward_imp
//此时返回imp为nil;
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
复制代码
疑问点二:resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?
实例方法的动态决议
阶段一:按照源码还原
通过源码已得知会给开发者一次弥补
的机会,通过resolveInstanceMethod
这个方法,这里在程序内模拟
一下这个方法;
@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
复制代码
控制台打印结果:
021-07-04 16:29:18.779380+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.779920+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.780020+0800 001-探索消息转发的起因[14290:1164992] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150
2021-07-04 16:29:18.780574+0800 001-探索消息转发的起因[14290:1164992] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150'
复制代码
依然报错
是在意料之中的事情,因为此时方法还并未实现
,但是在崩溃之前打印了我手动介入
的信息,也就是说,在崩溃之前有补救
的办法。
阶段二:实现\动态添加IMP
案例代码
@implementation FFPerson
//创建一个IMP
- (void)prettyGirls {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(likeGirls)) {
//当sel==likeGirls,动态给这个sel指定一个新的imp(prettyGirls)
IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
Method method = class_getInstanceMethod(self, @selector(prettyGirls));
const char * type = method_getTypeEncoding(method);
return class_addMethod(self, sel, prettyGirlsImp, type);
}
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
复制代码
控制台打印结果:
2021-07-04 16:46:17.907703+0800 001-探索消息转发的起因[14403:1174269] -[FFPerson prettyGirls]
复制代码
类方法的动态决议
案例代码
@interface FFPerson : NSObject
//声明了一个实例方法
+ (void)enjoyLife;
@end
@implementation FFPerson
//该类方法并没有实现
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//初始化FFPerson的实例对象
FFPerson *person = [FFPerson alloc];
//调用未实现的类方法
[FFPerson enjoyLife];
}
return 0;
}
复制代码
控制台经典报错
2021-07-04 17:15:18.903974+0800 001-探索消息转发的起因[14552:1188609] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188
2021-07-04 17:15:18.904697+0800 001-探索消息转发的起因[14552:1188609] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff205a56af __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff202dd3c9 objc_exception_throw + 48
2 CoreFoundation 0x00007fff20627bdd __CFExceptionProem + 0
3 CoreFoundation 0x00007fff2050d07d ___forwarding___ + 1467
4 CoreFoundation 0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
terminating with uncaught exception of type NSException
(lldb)
复制代码
阶段一:按照源码还原
通过源码已得知会给开发者一次弥补
的机会,通过resolveInstanceMethod
这个方法,这里在程序内模拟
一下这个方法;
@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
复制代码
控制台打印
2021-07-04 17:19:48.778680+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-_dynamicContextEvaluation:patternString:
2021-07-04 17:19:48.791081+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 17:19:48.791255+0800 001-探索消息转发的起因[14583:1191738] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x1000081a8
复制代码
依然报错
是在意料之中的事情,因为此时方法还并未实现
,但是在崩溃之前打印了我手动介入
的信息,也就是说,在崩溃之前有补救
的办法。
阶段二:实现\动态添加IMP
案例代码
@implementation FFPerson
//为元类对象创建创建一个IMP
+ (void)enjoyTime {
NSLog(@"%@ - %s",self,__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(enjoyLife)) {
IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
const char * type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
}
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
复制代码
控制台打印结果:
2021-07-04 17:40:30.032916+0800 001-探索消息转发的起因[14750:1203053] FFPerson - +[FFPerson enjoyTime]
复制代码
结论:
通过手动添加
resolveInstanceMethod/resolveClassMethod
,先打印验证在崩溃前有没有开发者的操作空间
通过判断当前查找的
sel
动态添加一个新的imp
经过论证,此方法是
可以
的。此时sel
(likeGirls/enjoyLife)对应实现的imp
不再是likeGirls/enjoyLife
,而是变成了我动态指定的prettyGirls/enjoyTime
疑问点三:类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?
整合实例对象/类对象的动态方法决议
回到现实,日常开发
中不可能每个类中都实现
一份resolveInstanceMethod/resolveClassMethod
,这是比较真实的场景,那么这里通过创建NSObject分类
的方式将此实现都放在分类中:
NSObject+FF
@implementation NSObject (FF)
//为实例对象创建一个IMP
- (void)prettyGirls {
NSLog(@"%s",__func__);
}
//为元类对象创建创建一个IMP
+ (void)enjoyTime {
NSLog(@"%@ - %s",self,__func__);
}
#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(likeGirls)) {
IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
Method method = class_getInstanceMethod(self, @selector(prettyGirls));
const char * type = method_getTypeEncoding(method);
return class_addMethod(self, sel, prettyGirlsImp, type);
} else if (sel == @selector(enjoyLife)) {
IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
const char * type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
}
return NO;
}
#pragma clang diagnostic pop
@end
复制代码
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
FFPerson *person = [FFPerson alloc];
//调用未实现的实例方法
[person likeGirls];
//调用未实现的类方法
[FFPerson enjoyLife];
}
return 0;
}
复制代码
打印结果(部分关键打印)
2021-07-04 18:06:55.491847+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 18:06:55.491952+0800 001-探索消息转发的起因[14926:1216030] -[NSObject(FF) prettyGirls]
2021-07-04 18:06:55.492192+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 18:06:55.502342+0800 001-探索消息转发的起因[14926:1216030] FFPerson - +[NSObject(FF) enjoyTime]
复制代码
疑问点四:动态方法决议的作用是什么?Apple为什么这么去设计?
oop与aop
-
oop:面向
对象
编程,什么人做什么什么事情,分工非常明确
。- 好处:
耦合
度很低 - 痛点:有很多
冗余
代码,常规解决办法是提取
,那么会有一个公共
的类,所有人对公共的类进行集成
,那么所有人对公共类进行强依赖
,也就代表着出现了强耦合
- 好处:
-
aop:面向
切面
编程,是oop的延伸
- 切点:要切入的
方法
和切入的类
,比如上述的例子中的enjoyLife
和FFPerson
- 优点:
对业务无侵入
,通过动态方式
将某些方法进行注入
- 缺点:做了一些判断,执行了很多
无关代码
,包括系统方法
,造成性能消耗
。会打断
apple的方法动态转发
流程。
- 切点:要切入的
解答在源码探索过程中提出的疑问点:
当方法未实现的时候为什么会报unrecognized selector sent to instance
这个错误呢?
通过上述的论证
可以知道当方法未实现
的时候,进入方法慢速查找
之后,依然找不到,会给方法一个默认值_objc_msgForward_impcache
,此默认值通过一系列的查找
,最终会找到objc_defaultForwardHandle
函数,此函数里面标注了明确的错误信息
。
resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?
通过调试发现了原因是多执行了respondsToSelector
等方法,具体原因未探索清楚,后续补充
类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?
正常的类对象
动态方法决议会进入resolveClassMethod
,这点是毋庸置疑
的,但是类方法查找过程是在元类
中查找,那么通过isa的指向图
中可以得知,类方法
的查找过程也是有继承关系
的,会一直向上找
,找到superMetaClass
,找到rootMetaClass
,最终找到NSObject
,到这一层,所有的方法对于NSObject
来讲都是实例方法
所以会调用resolveInstanceMethod
。
动态方法决议的作用是什么?Apple为什么这么去设计?
首先这是苹果给开发者
的最后一次机会
,让我们自行处理一下你找不到
的方法。将resolveInstanceMethod
方法写在NSNbject
的分类
中,意味着全局所有方法找不到
,我们都可以监听
的到。创建一个NSObject
分类“项目名_模块_事物”
,比如,FF_Home_didClickDetail
,此didClickDetail
事件未实现,那么第一时间
向后台报告,哪个项目
,哪个模块
,哪个时间导致了crash
,让你极致的拯救你的KPI
,第二件事就是防止carsh
,当时间未实现
的时候,跳转到Home
。
感悟:
通过此过程的探索,想要成为一个高逼格的开发人员,一定要给别人更多的容错处理。