在OC底层探索11-objc_msgSend慢速查找流程中解释了对方法的非缓存查询以及方法查找失败之后的系统报错。
如果在2种机制下都没有找到方法imp,苹果也给出了2条建议:
- 动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
resolveMethod_locked - 消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
1. 方法动态决议
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 动态方法决议 : 给一次机会 重新查询
if (! cls->isMetaClass()) { // 对象 - 类
resolveInstanceMethod(inst, sel, cls);
}
else { // 类方法 - 元类
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
// 此处cls已经是元类了。元类调用方法,根据isa关系会在根元类中查找方法。
//通过根元类的继承链最终找到NSObject,在NSObject中查询`实例方法`的resolve的实现。巧妙的设计!!
resolveInstanceMethod(inst, sel, cls);
}
}
// 在调用一次查询
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
- 此处的cls已经是指向
isa的指针,也就是类,元类,此逻辑是经过汇编层的加工。所以判断不是元类就调用实例方法的resolve实现;反之调用类方法的resolve实现. - 根据元类的iSA关系,最终会找到根元类(NSObject),因为类的根元类都是
NSObject。但(NSObject)类中只存在对象方法,所以需要再调用一次resolveInstanceMethod, - 在方法动态决议中,开发者会重新实现该
sel的imp所以, 需要重新进行一次查询。而且在本次查询中会优先在缓存中查找。
resolveInstanceMethod
实例方法的resolve具体源码实现
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//生成动态解析的方法Sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
//在当前类的元类中查找resolve方法是否找得到
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
return;
}
//resolveInstanceMethod消息发送
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//进行一次方法慢速查询,将当前方法插入缓存中,提高效率
IMP imp = lookUpImpOrNil(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 {
_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));
}
}
}
在
resolveInstanceMethod调用后,又调用了一次lookUpImpOrNil;我们知道该方法如果找到对应imp之后会插入到对象类的缓存中去,方便后续使用;另一个是方便debug进行打印。
如何打开该打印
可以通过查看log来发现实现了resolveInstanceMethod的方法
resolveClassMethod
类方法的resolve具体源码实现
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//判断resolveClassMethod方法是否实现
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
return;
}
//判断类是否实现
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);
//进行一次方法慢速查询,将当前方法插入缓存中,提高效率
IMP imp = lookUpImpOrNil(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));
}
}
}
- 实现逻辑于
resolveInstanceMethod基本相同
使用方式
- 实例方法的resolve
为self动态增加sayMaster的实现.使用runtime-api
+ (BOOL)resolveInstanceMethod:(SEL)sel{
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
- 当然也可以参考apple官方文档
- 类方法的resolve
+ (BOOL)resolveClassMethod:(SEL)sel{
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
- 唯一的区别就是类方法是需要添加到
元类中的,所以需要先找到元类objc_getMetaClass.
根据观察resolveInstanceMetho会走2次
-
第一次是在查询方法时
lookupimp中调用的
-
第二次是在coreFunction时调用的
- 在慢速转发过程中会进行第二次调用,后面会换种方式来验证
2.消息转发
在之前有提到apple推荐的快速转发、慢速转发,他们是何时调用的呢?是以什么方式调用的呢?现在就来讨论下~
方法一
不知在之前有没有留意一个方法log_and_fill_cache(...)在这个方法中我们发现了一个系统提供的log方法。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
- 是否打印就是看
objcMsgLogEnabled这个参数的值,系统并没有对外提供这个方法,但是我们可以自己导出.
调用方法

-
extern void instrumentObjcMessageSends(BOOL flag);这个方法就是系统提供,但是需要我们手动导出后使用的方法。 - 在我们调用方法的前后进行,可以看到该方法的所有调用记录
如何查看

结果

看到了熟悉的resolveInstanceMethod,而且出现了2次,也印证了之前的猜测。
与此同时还有些并不熟悉的方法forwardingTargetForSelector,methodSignatureForSelector。
方法二

看到了这个调用的堆栈信息,调用的是CoreFoundation库。可是apple爸爸并没有开源这个库,所以想要查看内部的调用就需要拿出最终大招Hopper反汇编。
- 在lldb调试中使用
image list查看CoreFoundation的库的本地地址。然后拖入Hopper中。
-
forwarding在可以看到
forwardingTargetForSelector的伪代码调用 这就是所谓快速转发流程


- 继续跟流程会走到
methodSignatureForSelector,以及forwardInvocation 这就是所谓慢速转发流程- 查看调用栈这两个方法中间应该会调用
resolveInstanceMethod,但是在反汇编中没有看到具体的调用,如果有知道的大佬可以提醒一下小弟。
消息转发简单实现
// 1: 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//此处返回一个实现该方法sel的对象
return [super forwardingTargetForSelector:aSelector];
}
// 2: 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//返回方法的参数编码
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
//更换方法接受者
anInvocation.target = [LGStudent alloc];
//更换方法索引
anInvocation.selector = NSSelectorFromString(@"实现了该IMP的SEL");
//更换方法参数编码
anInvocation.methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]
// anInvocation 保存 - 方法
[anInvocation invoke];
}
3. 整体流程图

消息实现未找到后的流程
-
forwardInvocation不处理并不会奔溃。



