探索Runtime动态方法解析与消息转发流程
在探索objc_msgSend函数的实现流程一文中,我们最后分析到了 lookUpImpOrForward 函数,该函数会从自己和父类的method_list中遍历寻找objc_msgSend中的SEL所需要的IMP,如果遍历结束后仍然未找到对应的IMP,就会进入动态方法解析流程,动态方法解析还未找见的话,就会进入消息转发流程,今天我们还是从runtime源码来着手分析,动态方法解析和消息转发的流程。
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //开始动态方法解析
runtimeLock.lock();
// 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;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache; //消息转发
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
动态方法解析
我们开始分析动态方法解析前,先要了解一些概念,先来查看一个类的结构:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
再来了解一下isa:isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。所以我们可以把类方法看做是其isa指向的元类的实例方法。看一张关系图(isa走位图):

举个例子说明一下:假设有个Person类继承自NSObject,再有一个Student类继承自Person,那么Student就是图中的SubClass,Person就是图中的SuperClass,NSObject便是其中的RootClass。
需要弄清的有两点:
- 所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。
- 根元类的父类是根类。
3.NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,所以如下调用是不会报错的
@interface NSObject (Add)
+(void)hello;
@end
@implementation NSObject (Add)
-(void)hello{
NSLog(@"hello...");
}
@end
//然后调用
[NSObject hello];
接下来我们进入正题,开始分析动听方法解析(_class_resolveMethod) 的执行流程:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //如果cls不是一个元类,说明调用的是一个实例方法
//此时 cls表示的是类,而inst代表的是类的实例
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
//此时 cls表示的是元类,而inst代表的是类
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
- 实例方法解析
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//cls->ISA() 此时指向的是元类,判断元类是否实现了resolveInstanceMethod方法(相当于该类判断是否有resolveInstanceMethod的类方法)
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//如果找到了 就重启消息发送流程
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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));
}
}
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
可以看到,又进入了 lookUpImpOrForward 方法去查找IMP,我们知道lookUpImpOrForward方法里如果没有递归查找到IMP就又会触发动态方法解析和消息转发,这样不是陷入死循环了吗?其实并不是这样,我们按执行顺序开始走一遍,先去元类里查找,然后再去元类的父类也就是根源类查找,都没有找到,再去根源类的父类也就是NSObject查找,我们查看NSObject的源码:

发现NSObject是实现了resolveInstanceMethod这个方法,只是返回了NO。系统就是通过这样的方式来避免产生死递归。
看源码可以看到,如果找到了resolveInstanceMethod方法,系统就会重新执行一遍 objc_msgSend 的流程,去回调这个方法
- 类方法解析
看源码
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
1、_class_resolveClassMethod:开始从元类里查找resolveClassMethod方法,一直递归去查找,这里如果找到了,依然会重启消息发送方法
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
//此时的cls是元类,inst是类
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
注意:跟实例方法不同的是:类方法重启objc_msgSend时传入的参数是_class_getNonMetaClass(cls, inst),而实例方法重启时传的参数是cls(类对象),下面我们来分析一下_class_getNonMetaClass的实现:
Class _class_getNonMetaClass(Class cls, id obj)
{
mutex_locker_t lock(runtimeLock);
cls = getNonMetaClass(cls, obj);
assert(cls->isRealized());
return cls;
}
static Class getNonMetaClass(Class metacls, id inst)
{
static int total, named, secondary, sharedcache;
runtimeLock.assertLocked();
realizeClass(metacls);
total++;
//metacls是元类 inst是类
// 如果metacls不是元类了,说明此时传进来的inst类是NSObject,就直接返回元类
if (!metacls->isMetaClass()) return metacls;
// metacls really is a metaclass
// special case for root metaclass
// where inst == inst->ISA() == metacls is possible
//如果元类的isa指向了自己,说明此时元类是根元类 ,返回它的父类 即NSObject
if (metacls->ISA() == metacls) {
Class cls = metacls->superclass;
assert(cls->isRealized());
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
if (cls->ISA() == metacls) return cls;
}
//一般情况
if (inst) {
Class cls = (Class)inst;
realizeClass(cls);
// cls may be a subclass - find the real class for metacls
while (cls && cls->ISA() != metacls) {
//这里主要判断isa是否被篡改 — 异常判断
cls = cls->superclass;
realizeClass(cls);
}
if (cls) {
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
//直接返回cls 类对象
return cls;
}
#if DEBUG
_objc_fatal("cls is not an instance of metacls");
#else
// release build: be forgiving and fall through to slow lookups
#endif
}
// try name lookup
{
Class cls = getClass(metacls->mangledName());
if (cls->ISA() == metacls) {
named++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful by-name metaclass lookups",
named, total, named*100.0/total);
}
realizeClass(cls);
return cls;
}
}
// try secondary table
{
Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
if (cls) {
secondary++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful secondary metaclass lookups",
secondary, total, secondary*100.0/total);
}
assert(cls->ISA() == metacls);
realizeClass(cls);
return cls;
}
}
// try any duplicates in the dyld shared cache
{
Class cls = nil;
int count;
Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count);
if (classes) {
for (int i = 0; i < count; i++) {
if (classes[i]->ISA() == metacls) {
cls = classes[i];
break;
}
}
free(classes);
}
if (cls) {
sharedcache++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful shared cache metaclass lookups",
sharedcache, total, sharedcache*100.0/total);
}
realizeClass(cls);
return cls;
}
}
_objc_fatal("no class for metaclass %p", (void*)metacls);
}
可以看到,_class_getNonMetaClass的正常流程还是返回了类对象,为什么要返回类对象呢,我们正常的思路是谁出问题,在谁那里修复(应该在元类里取修复),但是类方法是在元类中的,元类是一个虚拟的类,没办法进行书写,所以苹果就设计了这么一个方式,直接返回类,在类里面去实现resolveClassMethod
2、但是我们看类方法的_class_resolveMethod里发现,_class_resolveClassMethod执行完了如果没有找到的话,还有一步操作_class_resolveInstanceMethod(与上面实例方法步骤一致),该方法会继续从元类里寻找resolveInstanceMethod方法,直到NSObject,这也证明了我们前面的NSObject (Add)类的代码运行不会报错。
综上所述,我们可以在NSObject扩展里实现resolveInstanceMethod方法,就可以拦截所有的未实现方法。
消息转发
如果动态方法解析没有进行任何处理,那么就会进入消息转发流程:
imp = (IMP)_objc_msgForward_impcache;
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
发现此处被苹果闭源了,那么就没有办法了吗???
我们写一个测试代码
2019-03-02 17:15:45.456611+0800 CrashDemo[42591:4806004] -[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000
2019-03-02 17:15:45.464384+0800 CrashDemo[42591:4806004] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000'
*** First throw call stack:
(
0 CoreFoundation 0x00000001096301bb __exceptionPreprocess + 331
1 libobjc.A.dylib 0x00000001086f3735 objc_exception_throw + 48
2 CoreFoundation 0x000000010964ef44 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 UIKitCore 0x000000010bfeab4a -[UIResponder doesNotRecognizeSelector:] + 287
4 CoreFoundation 0x0000000109634ed6 ___forwarding___ + 1446
5 CoreFoundation 0x0000000109636da8 _CF_forwarding_prep_0 + 120
6 CrashDemo 0x0000000107dd67f0 main + 80
7 libdyld.dylib 0x000000010aac9575 start + 1
8 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
查看堆栈信息可以看出是在CoreFoundation下执行了_forwarding_prep_0、forwarding等方法,所以我们来尝试反编译CoreFoundation的可执行文件来尝试一下是否可以找到一些信息:

还真的被我们找到了_forwarding_prep_0这个信息,点进去:
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
*(rsp + 0xa0) = zero_extend_64(xmm7);
*(rsp + 0x90) = zero_extend_64(xmm6);
*(rsp + 0x80) = zero_extend_64(xmm5);
*(rsp + 0x70) = zero_extend_64(xmm4);
*(rsp + 0x60) = zero_extend_64(xmm3);
*(rsp + 0x50) = zero_extend_64(xmm2);
*(rsp + 0x40) = zero_extend_64(xmm1);
*(rsp + 0x30) = zero_extend_64(xmm0);
stack[2021] = arg0;
rax = ____forwarding___(rsp, 0x0);
if (rax != 0x0) {
rax = *rax;
}
else {
rax = objc_msgSend(stack[2021], stack[2021]);
}
return rax;
}
再点进forwarding:
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
r13 = arg0;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rbx = *(r13 + rcx * 0x8);
var_40 = *(r13 + rcx * 0x8 + 0x8);
r15 = rcx * 0x8;
if (rbx >= 0x0) goto loc_12c9ba;
loc_12c9ba:
var_48 = r15;
r15 = rsi;
var_38 = r12;
r12 = object_getClass(rbx);
var_50 = class_getName(r12);
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_12ca57;
会发现这么一段,if (class_respondsToSelector(r12,@selector(forwardingTargetForSelector:)) == 0x0)
如果forwardingTargetForSelector方法实现了的话,会继续进行转发,如果没有实现的话,会发现还有这么一段代码:
loc_12ca78:
var_48 = r13;
if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_12ce0d;
loc_12ce0d:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(var_38);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_38, r14, object_getClassName(var_38), r9, stack[2037]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_38, r14, r8, r9, stack[2037]);
}
goto loc_12ce6d;
loc_12ce6d:
r15 = sel_getName(var_40);
r8 = sel_getUid(r15);
if (r8 != var_40) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_40, r15, r8, r9, stack[2037]);
}
if (class_respondsToSelector(object_getClass(var_38), @selector(doesNotRecognizeSelector:)) != 0x0) {
[var_38];
asm { ud2 };
rax = loc_12ced8(rdi, rsi);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", var_38, object_getClassName(var_38), r8, r9, stack[2037]);
asm { ud2 };
rax = loc_12ceff();
}
return rax;
loc_12ca96:
r12 = var_38;
r14 = [r12 methodSignatureForSelector:var_40];
if (r14 == 0x0) goto loc_12ce6d;
loc_12cab6:
rbx = [r14 _frameDescriptor];
if (((*(int16_t *)(*rbx + 0x22) & 0xffff) >> 0x6 & 0x1) != r15) {
rax = sel_getName(var_40);
r8 = "";
rcx = r8;
if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
rdx = rax;
if (r15 == 0x0) {
r8 = " not";
}
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[2037]);
}
var_50 = rbx;
if (class_respondsToSelector(object_getClass(r12), @selector(_forwardStackInvocation:)) != 0x0) {
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
r13 = [NSInvocation requiredStackSizeForSignature:r14];
rdx = *____forwarding___.invClassSize;
r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
memset(r12, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r12);
var_40 = r13;
[r12 _initWithMethodSignature:r14 frame:var_48 buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
[var_38 _forwardStackInvocation:r12];
r15 = 0x1;
}
else {
if (class_respondsToSelector(object_getClass(r12), @selector(forwardInvocation:)) != 0x0) {
rax = [NSInvocation _invocationWithMethodSignature:r14 frame:var_48];
rdi = r12;
r12 = rax;
[rdi forwardInvocation:r12];
}
else {
rcx = object_getClassName(r12);
rdx = r12;
r12 = 0x0;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", rdx, rcx, r8, r9, stack[2037]);
}
var_40 = 0x0;
r15 = 0x0;
}
rax = var_50;
可以猜出来,r14即是methodSignatureForSelector:获取到的方法前面,然后接下来会判断并最后执行forwardInvocation进行消息重定向。
至此,iOS Runtime动态方法解析和消息转发流程已执行完成。最后一步消息转发是闭源的,过程比较模糊,只作为一个了解,我们也可以通过
instrumentObjcMessageSends(YES);
test code
instrumentObjcMessageSends(NO);
去打印runtime消息执行并生成文件,文件目录在/private/tmp/ 文件夹,找到最新的 msgSends-xxxx文件同样可以看到消息转发的执行流程。