前言程序员
在iOS 底层探索篇 —— 方法的查找流程这篇文章中,咱们已经知道了方法的查找的流程了,若是方法没有查找到,在lookUpImpOrForward()
函数里面还有一部分是留给查找失败的处理。缓存
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...省略部分代码...
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver did not help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
复制代码
// No implementation found. Try method resolver once.
这一段注释告诉咱们,imp
没有,就仍是方法解析。// No implementation found, and method resolver did not help. // Use forwarding.
这一段又是告诉咱们,方法解析没有帮到咱们,就利用forwarding
转发。
接下来咱们开始去分析下面两个流程。bash
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
triedResolver = YES;
goto retry;
}
复制代码
_class_resolveMethod
就是咱们须要研究的函数。函数
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst); // 已经处理
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 对象方法 决议
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
复制代码
区分是元类仍是类post
- 对象方法解析
_class_resolveInstanceMethod
。- 类方法解析
_class_resolveClassMethod
。而且类方法解析之后,依旧没有找到imp
,仍是会走对象方法解析。
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
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 does not fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
...省略部分代码...
}
复制代码
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id 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);
// Cache the result (good or bad) so the resolver does not fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
...省略部分代码...
}
复制代码
两个函数实际上是相似的。ui
if (! lookUpImpOrNil()
这一个条件判断,主要是防止程序员写的类 不是继承自NSObject
,就不须要开始动态方法解析流程了。(typeof(msg))objc_msgSend
,一个objc_msgSend
消息发送SEL_resolveInstanceMethod
或者SEL_resolveClassMethod
。- 在代码的注释段咱们能够看到
resolveInstanceMethod
或者resolveClassMethod
这么两个方法,意思就是告诉咱们能够本身去实现父类NSObject
的方法。
lookUpImpOrNil
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;
}
复制代码
动态方法解析以后,在开始查找流程,回到了iOS 底层探索篇 —— 方法的查找流程这篇文章的方法的慢速查找阶段。spa
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(saySomething)) {
NSLog(@"说话了");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
复制代码
将没有实现的方法
saySomething
,用另一个已经实现了方法sayHello
来替换。3d
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayLove)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method method = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *types = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("LGStudent"), sel, imp, types);
}
return [super resolveClassMethod:sel];
}
复制代码
这里须要注意的位置就是,因为类方法在元类里面存着,咱们添加方法的时候要在类的元类里面添加才能作到动态方法解析的成功。 日志
这个问题留到最后面解释。code
_class_resolveClassMethod(cls, sel, inst); // 已经处理
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 对象方法 决议
_class_resolveInstanceMethod(cls, sel, inst);
}
复制代码
- 咱们经过iOS 底层探索篇 —— isa的初始化&指向分析这篇文章的分析,无论是类(针对对象方法),仍是元类(针对类方法),他们最终的父类都会指向
NSObject
。- 在方法的查找流程中,咱们探索出来,其实是经过
sel
的name
来匹配的,在底层不分-、+方法的。- 目的就是可让咱们在
NSObject
中作相同的处理。
若是动态方法解析没有处理,接下来会来到消息转发。
在iOS 底层探索篇 —— 方法的查找流程这篇文章中,咱们知道方法查找到了以后就会进入缓存流程。
static void
log_and_fill_cache(Class cls, Class implementer, Method meth, SEL sel)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
_cache_fill (cls, meth, sel);
}
复制代码
if (objcMsgLogEnabled)
若是这个条件成立,那么就会进行日志打印。
进行所有搜索objcMsgLogEnabled
,能够发现
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
复制代码
- 条件赋值的函数。
- 查看函数定义
OBJC_EXPORT void instrumentObjcMessageSends(BOOL flag)
是一个可供外部使用的。
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
复制代码
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
复制代码
/tmp/msgSends
这里是日志存放的位置。instrumentObjcMessageSends
方法须要定义一下extern
表示外部使用。commond + shift + G
查看/tmp/msgSends
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class
+ __NSCFString NSObject resolveInstanceMethod:
+ __NSCFString NSObject resolveInstanceMethod:
复制代码
每一个方法会打印两次的缘由是,本身自己有一次,还有一次是
super
给干出来的。
- 经过日志分析咱们能够看到流程是
resolveInstanceMethod
到forwardingTargetForSelector
到methodSignatureForSelector
到doesNotRecognizeSelector
,每一步都没有实现就会报错了。
这里注意了,能够解决`4.3`遗留下来的问题,为何会进来两次。看日志文件还有一次是__NSCFString系统级别处理了一次。
经过上面的日志咱们能够看到快速转发流程forwardingTargetForSelector
。
NSObject.mm
文件查看定义+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
复制代码
可让子类本身实现一下。
意思就是一个没法识别的消息可让其余的对象来处理这个消息。
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
[student saySomething];
}
return 0;
}
@interface LGTeacher : NSObject
@end
@implementation LGTeacher
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
@implementation LGStudent
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
复制代码
LGStudent
这个类没有实现该saySomething
的方法,可是LGTeacher
实现了,就直接交给LGTeacher
的对象处理。
若是快速转发阶段也没有实现,就会进入到慢速转发阶段
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(saySomething)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
复制代码
这个位置就是对这个方法签名以后 丢出去,谁想要处理就去处理。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
复制代码
若是可让其余人处理就丢给另外的处理,不然系统就不处理这个方法了。
消息转发的分析到这里就结束了,下面作一下总结。
objc_msgSend
方法快速查找和慢速查找不到结果以后就进入消息动态解析。_class_resolveMethod()
,有处理就按照处理的来,没有处理,就进入消息快速转发阶段。forwardingTargedForSelector()
,有处理,就按照交给处理的对象来实现,有没有交给其余对象处理,进入慢速转发阶段。methodSignatureForSelector()
,进行方法签名,把方法丢出去,forwardInvocation()
来对消息处理。doesNotRecognizeSelector
报错。附上流程图以下:
控制台的打印顺序是按照
resolveInstanceMethod
->forwardingTargetForSelector
->methodSignatureForSelector
->resolveInstanceMethod
->forwardInvocation
。
- 倒数第二步把动态方法解析又调用了一次,这里其实是上一步返回了一个方法签名以后,下一步就会查找这个签名,查找流程就会在走一次,这就是签名匹配的过程,再次调用
_class_getInstanceMethod
是系统级别作的。