在OC底层探索11-objc_msgSend慢速查找流程中解释了对方法的非缓存查询以及方法查找失败之后的系统报错。
如果在2种机制下都没有找到方法imp
,苹果也给出了2条建议:
resolveMethod_locked
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);
}
isa
的指针,也就是类
,元类
,此逻辑是经过汇编层的加工。所以判断不是元类就调用实例方法
的resolve实现;反之调用类方法
的resolve实现.NSObject
。但(NSObject)类中只存在对象方法,所以需要再调用一次resolveInstanceMethod
,sel
的imp
所以, 需要重新进行一次查询。而且在本次查询中会优先在缓存中查找。实例方法的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
的方法
类方法的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
基本相同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);
}
+ (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
.lookupimp
中调用的在之前有提到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
反汇编。
image list
查看CoreFoundation
的库的本地地址。然后拖入Hopper
中。
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];
}
消息实现未找到后的流程
forwardInvocation
不处理并不会奔溃。