1.动态方法决议分析
之前我们在消息的慢速查找中,对于没有找到的方法,苹果会给我们1次机会进行弥补就是之前慢速查找的resolveMethod_locked(inst, sel, cls, behavior)
方法动态决议。
我们日常开发如果方法没有实现的话会报错,方法没有找到
1.1崩溃日志打印分析
是因为走到方法转发中_objc_msgForward_impcache
进入源码可知:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
复制代码
主要调用了_objc_forward_handler
方法,全局搜素得到
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
复制代码
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
复制代码
就是平常我们方法找不到调用的方法,添加了当前的类和方法等信息。会在lldb打印出崩溃的信息
。那么我们是否可以拦截防止崩溃呢。先看下动态方法决议流程
1.2 resolveMethod_locked分析
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());//
runtimeLock.unlock();
//当前类的isa 不是元类的话
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);//进入实例方法决议
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);//进入类方法决议
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {//如果没有在类方法中找到解决的方法决议
resolveInstanceMethod(inst, sel, cls);//再次去调用实例方法决议
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it调用解析器可能会填充缓存,因此再去查找一下
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);//
}
复制代码
- 因为在oc中方法存在
实例方法
和类方法
,所以先判断当前类的isa
是不是元类
,不是的话走resolveInstanceMethod
是的话先走resolveClassMethod
。 - 在元类判断中,调用
resolveClassMethod
没有解决,再次调用resolveInstanceMethod
- 最后在去类的方法列表里查找下我们替换的方法,走慢速查找流程。
我们在KBStudent中重写一下这个resolveInstanceMethod
方法
确实进来了调用了KBStudent的resolveInstanceMethod
方法,但是为什么走了2
次。
1.2.1 resolveInstanceMethod分析
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);//定义解决方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.去查找一下有没有解决问题这个方法,没有的的话,就返回,但是这个方法基本上不会进来的,系统在NSObject中有默认实现这个方法
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//对着这个方法发送消息看下有没有解决问题
/* + (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}*/
bool resolved = msg(cls, resolve_sel, sel);// return Yes就是说你处理了,系统默认是NO,你没处理
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);//再次慢速查找这个解决方法的imp,重新指向的方法。
if (resolved && PrintResolving) {
if (imp) {//操作的信息写入
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
//有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));//没有imp
}
}
}
复制代码
- 在调用实例方法的时候没有实现,进入实例方法的动态决议。我们是否实现了
resolveInstanceMethod
,但是通常NSObject
会实现这个方法返回NO
,没有处理。 - 对
resolveInstanceMethod
发送消息,再次慢速查找这个解决方法的imp但是不会进行消息转发,重新指向的方法。imp 查找过程lookUpImpOrNilTryCache
->_lookUpImpTryCache
->lookUpImpOrForward
- 添加的解决方法的话
lookUpImpOrForwardTryCache
->_lookUpImpTryCache
再次慢速查找Imp找到进行消息发送。
1.2.2打印2次的原因
正常实例对象的话没有找到方法的话就会在慢速查找中走resolveInstanceMethod
方法,但是走了2次说明,还有别的地方再次调用了resolveInstanceMethod
方法。我们在消息转发的地方打上一个断点,进行调试
全局搜搜class_getInstanceMethod
得到
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.查找方法列表,尝试去找一下方法解析
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
复制代码
调试源码的时候有的时候会有很多系统方法,导致我们过程很烦,容易错过。可以直接在自己新建的工程打断点打印内存信息
这是一个NSObejct
的实例方法,我们在使用实例对象调用方法的时候出错,自身第一次没解决的话
,CoreFoundation
应该再次调用了class_getInstanceMethod
方法,里面的 lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER)
的调用是我们再次调用了上面的流程;
1.2.3 解决方案
我们可以把未实现的方法给他重新定义,给他一个新的Imp,解决崩溃
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@来了",NSStringFromSelector(sel));
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));
Method method = class_getInstanceMethod(self, @selector(sayNotNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel , imp, type);
}
return [super resolveInstanceMethod:sel];
}
-(void)sayNotNB
{
NSLog(@"%s",__func__);
}
复制代码
1.2.3 类方法未实现解决
同样的如果是类方法未实现,崩溃解决
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
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
类方法
- 在调用类方法的时候没有实现,进入类方法的动态决议。我们是否实现了
resolveClassMethod
,但是通常NSObject
会实现这个方法返回NO
,没有处理。 - 对
resolveClassMethod
发送消息,再次慢速查找这个解决方法的imp但是不会进行消息转发,重新指向的方法,也就是重定向。imp 查找过程lookUpImpOrNilTryCache
->_lookUpImpTryCache
->lookUpImpOrForward
- 添加的解决方法的话
lookUpImpOrForwardTryCache
->_lookUpImpTryCache
再次慢速查找Imp找到进行消息发送。 - 没有解决的话或者为空,会去元类的对象中查找调用实例方法决议
走了2次resolveClassMethod
方法,说明和上面实例方法一样调用了class_getInstanceMethod
打印一下确实这样
+(BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"%@来了",NSStringFromSelector(sel));
if (sel == @selector(sayHello)) {
Class metaClass = object_getClass(self);
IMP imp = class_getMethodImplementation(metaClass, @selector(sayLoveYou));
Method method = class_getClassMethod(metaClass, @selector(sayLoveYou));
const char* type = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, type);
}
return [super resolveClassMethod:sel];
}
+(void)sayLoveYou{
NSLog(@"%s",__func__);
}
复制代码
添加类方法的话要把类改成元类,类方法在元类方法列表中。
2.统一的处理AOP思想
因为我们开发中使用的对象懂继承于NSObject
,那么时不是可以在NSObject
的解决这个问题,方法查找的过程是沿着父类链进行查找。我们创建NSObject
的Category
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@来了",NSStringFromSelector(sel));
if (sel == @selector(sayHello)) {
IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));
Method method = class_getInstanceMethod(self, @selector(sayNotNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel , imp, type);
}else if(sel == @selector(sayNB))
{
IMP imp = class_getMethodImplementation(self, @selector(sayNotNB));
Method method = class_getInstanceMethod(self, @selector(sayNotNB));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel , imp, type);
}
return NO;
}
-(void)sayNotNB
{
NSLog(@"%s",__func__);
}
复制代码
类方法还是实例方法都避免了崩溃。为什么不用resolveClassMethod
方法?因为
resolveClassMethod(inst, sel, cls);//进入类方法决议
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {//如果没有在类方法中找到解决的方法决议
resolveInstanceMethod(inst, sel, cls);//再次去调用实例方法决议
}
复制代码
所以NSObject
不管是类方法还是实例方法最终都会走resolveInstanceMethod
。根元类的父类是NSObject,在oc中NSObject没有父类,他就是所有对象的父类
。
这就是AOP思想,面向切面编程。无侵入,不耦合。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
3.总结
- 在慢速查找中,如果没有找到,苹果会给你一次机会,去重写
resolveInstanceMethod
和resolveClassMethod
方法。 class_addMethod
注意当前是类方法还是实例方法。resolveMethod_locked
走2次是因为一次是正常的查找当前类的方法没查到进入,第二次是CoreFoundation
调用class_getInstanceMethod
进行动态决议流程- 在oc中对象都继承于NSObject,所以我们可以在
Category
统一处理resolveInstanceMethod