前言
OC是个运行时语言,它的运行时主要靠runtime库。而一个对象调用一个方法的时候,实际上是向对象发送一个消息。那么,若是找不到对应的方法的话,会发生什么现象呢?
方法查询
从上一篇我们可以看到方法的查询是沿着supclass向源头查找的,而最后找不到的话才会进入消息转发流程。那么方法的查询是哪个函数呢?
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
这个函数用于方法的查询,它的内部实现进行精简的话是这样的:
1.查询方法缓存
// 查询方法缓存
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
2.沿着superclass查找方法列表
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
3.查找不到方法,动态方法解析(resolveMethod)
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
//方法处理
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
//进入第二步,重新查找方法
goto retry;
}
4.重定向
既没有实现,又没有处理,则Use forwarding。
imp = (IMP)_objc_msgForward_impcache;
//将方法添加到缓存中
cache_fill(cls, sel, imp, inst);
到这里还没进入消息转发的流程,这里的处理主要是查找对应的方法。那么关于网上所说的消息转发是怎么了解到的呢?我觉得是看官方文档了解到的。(我实在没找到相应的源码_)
消息转发
根据文档的描述,当上面的函数还是没找到消息的话就会进入消息转发流程。
消息转发流程有两个重要的方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
-
具体流程如下:
当之前的forwoardingTargetForSelector
找不到转发对象的时候,才会进入之后的消息转发流程。
先进行方法的签名methodSignatureForSelector
,将方法包装秤NSInvocation
对象。
然后才进入正式的转发流程forwardInvocation
,在这里可以进行方法的最终处理。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
当然,到了消息转发的步骤后,消耗的资源就比较多了。但是使用消息转发机制可以做到一个很好的防御策略,用来防止查不到对应的方法造成的崩溃。