前言程序員
在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
是系統級別作的。