消息查询流程
前面研究了消息查询的正常流程消息查询流程:快速消息查询和慢速消息查询。如果还没有找到改消息IMP
那么会进入异常流程处理。
异常流程
- 消息动态决议方法:
resolveInstanceMethod
和resolveClassMethod
- 消息转发流程
forwardingTargetForSelector
案例
执行一个未实现的方法,如下所示。
报错,而且类中的方法执行了2次
//执行了2次
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@=======",NSStringFromSelector(sel));
return NO;
}
复制代码
查看源码中的流程
异常情况下进行方法决议获取IMP
这是慢速查询后没有找到IMP的处理
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
...
// 没有找到方法,尝试一次方法解析 单利
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//动态方法决议的控制条件,表示流程只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
...
}
复制代码
实例方法和类方法的决议入口,
- 判断cls是否是元类
- 不是元类,说明调用的是实例方法,会走
resolveInstanceMethod
- 如果是元类,说明调用的方法是类方法,会先调用
resolveClassMethod
查询元类中的类方法,如果找到了会缓存到cache
。如果缓存中没有,会查询元类的实例方法,走resolveInstanceMethod
。
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
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
// 快速查找和慢速查找sel对应的imp返回imp 实际上就是从缓存中取,因为前面已经缓存过了
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
实例方法查询流程
- 查询元类中是否有
resolveInstanceMethod
方法的缓存,有的话直接返回 - 如果没有会发送消息调用该方法
第一次执行
,调用后会缓存起来(这里可以做方法交换等) - 再次查询是否有
sel
的方法缓存(lookUpImpOrNilTryCache
)。经快速查询->慢速查询->再次来到方法决议。 - 如果实现了,缓存中没有。进入
lookUpImpOrForward
查找到sel对应imp插入缓存,调用imp查找流程结束 - 如果没有实现,缓存中没有。进入
lookUpImpOrForward
查找,sel没有查找到对应的imp,此时imp = forward_imp
,动态方法决议只调用一次
,此时会走done_unlock和done流程,既sel和forward_imp插入缓存,进行消息转发后还会调用一次
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//只要cls的元类初始化 resolve_sel就一定实现了,因为NSObject默认实现了resolveInstanceMethod方法
//目前是将resolveInstanceMethod方法缓存到cls的元类中
//通过lookUpImpOrNilTryCache的数我们知道`resolve_sel`是类方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//发送消息调用resolveInstanceMethod方法
//通过 objc_msgSend 发送消息 接收者是cls说明是类方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//判断 resolve_sel 方法有没有实现,注意是`resolve_sel`方法
bool resolved = msg(cls, resolve_sel, sel);//
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//为什么还有调用 lookUpImpOrNilTryCache 查询缓存和慢速查找呢
//虽然 resolveInstanceMethod 方法调用了。但是里面不一定实现了sel的方法
//所以还是再次要去查找sel对应的imp,如果没有实现就会把imp = forward_imp 插入缓存中
//因为慢速查找流程动态决议方法已经走过了,此时imp = forward_imp走down和down_unlock
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
复制代码
类方法查询
static void resolveClassMethod(id inst, SEL sel, Class cls){
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//当你为实现resolveInstanceMethod的时候,此处也不会进入return
//因为系统给resolveInstanceMethod函数默认返回NO,即默认实现了
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) {
...
}
}
复制代码
lookUpImpOrNilTryCache方法
主要作用通过behavior
来控制插入缓存,不管sel对应的imp有没有实现,还有就是如果imp返回了有值那么一定是在动态方法决议中动态实现了imp
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
// LOOKUP_NIL = 4 没有传参数behavior = 0 0 | 4 = 4
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
复制代码
首先最后一个参数默认是behavior = 0
,LOOKUP_NIL = 4, behavior|LOOKUP_NIL
大于等于LOOKUP_NIL
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//cls 是否初始化
if (slowpath(!cls->isInitialized())) {
// 没有初始化就去查找 lookUpImpOrForward 查找时可以初始化
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//在缓存中查找sel对应的imp
IMP imp = cache_getImp(cls, sel);
// imp有值 进入done流程
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
// 缓存中没有查询到imp 进入慢速查找流程
// behavior = 4 ,4 & 2 = 0 不会进入动态方法决议,所以不会一直循环
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//(behavior & LOOKUP_NIL) = 4 & 4 = 1
//LOOKUP_NIL 只是配合_objc_msgForward_impcache 写入缓存
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
复制代码
动态添加方法
- 动态添加方法后,会执行sayNB的方法。
- 执行了一次
resolveClassMethod
方法,没有执行resolveInstanceMethod
方法。是查询的是元类中的实例方法。而resolveInstanceMethod
是类方法。 - 实例方法查询流程:对象 -> 类 -> 父类 -> NSObject -> nil
- 类方法查询流程:类 -> 元类 -> 根元类 -> NSObject -> nil
- 所以,可以在
NSObject
的分类中的resolveInstanceMethod
方法中统一进行处理,但是很影响性能。
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@=======",NSStringFromSelector(sel));
if(sel == @selector(sayHappy)){
IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("ZMTeacher"), @selector(sayKC));
Method method = class_getInstanceMethod(objc_getMetaClass("ZMTeacher"), @selector(sayKC));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("ZMTeacher"), sel, sayNBImp, type);
}
return NO;
}
复制代码
OOP(面向对象编程) 和 AOP (面向切面编程)的区别
如果在NSObject
中统一处理resolveInstanceMethod
的方法就是属于面向切面编程。
- OOP:面向对象编程,什么人做什么什么事情,分工非常明确。对于特定的事件耦合度低,对于功能相同的地方,需要提取到特定的类,对于该类属于强依赖,耦合度高。
- AOP:动态切入到特定类的方法内,对代码侵入性比较小。
总结
- 方法执行,就是查询方法
IMP
的过程。走的是继承链。 - 在动态方法决议中,会执行
resolveInstanceMethod
或者resolveClassMethod
可以进行一次动态添加方法,避免崩溃。 - 方法查询经过了快速查找,慢速查找和动态方法决议。如果还是没有找到会进入消息转发。
- 消息转发后还会执行又一次避免崩溃的方法
forwardingTargetSelector
(在下一章)。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END