在前面兩篇文章OC底層探索(八)objc_msgSend 流程之方法快速查詢和OC底層探索(九)objc_msgSend 流程之方法慢速查詢中,分別分析了objc_msgSend的快速查詢和慢速查詢,在這兩種都沒找到方法實現的情況下,蘋果給了兩個建議
Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;
+ (void)sayNB;
+ (void)lgClassMethod;
@end
Person.m
@implementation Person
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [Person alloc];
//呼叫實體方法
[person say666];
//呼叫類方法
[Person sayNB];
}
return 0;
}
在文章OC底層探索(九)objc_msgSend 流程之方法慢速查詢中,我們介紹慢速查詢的時候,沒有找到imp,那麼蘋果就會給一次機會,挽救一下。—— 【第一次機會】
原始碼
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//物件 -- 類
if (! cls->isMetaClass()) { //類不是元類,呼叫物件的解析方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {//如果是元類,呼叫類的解析方法, 類 -- 元類
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
//為什麼要有這行程式碼? -- 類方法在元類中是物件方法,所以還是需要查詢元類中物件方法的動態方法決議
if (!lookUpImpOrNil(inst, sel, cls)) { //如果沒有找到或者為空,在元類的物件方法解析方法中查詢
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//如果方法解析中將其實現指向其他方法,則繼續走方法查詢流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
分析
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [Person alloc];
[person say666];
[Person sayNB];
}
return 0;
}
列印結果
呼叫實體方法say666報的錯
呼叫類方法sayNB報的錯
分析
由於在快速查詢和慢速查詢時,都沒有找到say666實體方法和sayNB類方法的實現,所以程式崩潰。
resolveInstanceMethod的原始碼
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// lookup resolveInstanceMethod
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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(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 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));
}
}
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ say666來了",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
列印結果:
分析
· 在執行實體方法say666時,在快速、慢速查詢都沒有找到的情況下,就會執行到resolveInstanceMethod方法。但是我們檢視列印,執行了兩遍這個方法,第一次執行是在慢速查詢結束後執行的,那麼第二次是在什麼時候呢?此問題我們先記錄一下。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
//獲取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
//獲取sayMaster的實體方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
//獲取sayMaster的豐富簽名
const char *type = method_getTypeEncoding(sayMethod);
//將sel的實現指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
列印結果:
分析:
· 在第一次動態方法決議時,返回sayMaster方法的imp,那麼此時說明已經找到一個方法的IMP,直接返回imp。
針對類方法,與實體方法類似,同樣可以通過重寫resolveClassMethod類方法來解決前文的崩潰問題,即在LGPerson類中重寫該方法,並將sayNB類方法的實現指向類方法lgClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
列印結果:
分析:
resolveClassMethod類方法的重寫需要注意一點,傳入的cls不再是類,而是元類,可以通過objc_getMetaClass方法獲取類的元類,原因是因為類方法在元類中是實體方法
在原始碼中,如果是元類的話,會有下面這行程式碼,如果在沒有找到類方法,那麼就會在元類的物件方法解析方法中查詢。
if (!lookUpImpOrNil(inst, sel, cls)) { //如果沒有找到或者為空,在元類的物件方法解析方法中查詢
resolveInstanceMethod(inst, sel, cls);
}
· 在isa走點陣圖中,繼承關係是:元類 -> 根元類 -> NSObject -> nil。元類最終繼承於NSObject,那麼我們在NSObject中實現resolveInstanceMethod,那麼類方法也會最終執行到NSObject中。
NSObject分類
@implementation NSObject (LG)
// 呼叫方法的時候 - 分類
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 來了",NSStringFromSelector(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);
}
else if (sel == @selector(sayNB)) {
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);
}
return NO;
}
@end
分析:
· 列印結果中呼叫NSObject的resolveInstanceMethod方法,
· 但是也列印一些不相關的資訊,這些資訊說明了別的方法也會走到此方法中。
在慢速查詢的流程中,我們瞭解到,如果快速+慢速沒有找到方法實現,動態方法決議也不行,就使用訊息轉發,但是,我們找遍了原始碼也沒有發現訊息轉發的相關原始碼,可以通過以下方式來了解,方法呼叫崩潰前都走了哪些方法。
流程圖:
1、開啟 objcMsgLogEnabled 開關,即呼叫instrumentObjcMessageSends方法時,傳入YES
2、在main中通過extern 宣告instrumentObjcMessageSends方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
通過logMessageSend原始碼,瞭解到訊息傳送列印資訊儲存在/tmp/msgSends 目錄,如下所示
執行程式碼,並前往/tmp/msgSends 目錄,發現有msgSends開頭的紀錄檔檔案,開啟發現在崩潰前,執行了以下方法
· 兩次動態方法決議:resolveInstanceMethod方法
· 兩次訊息快速轉發:forwardingTargetForSelector方法
· 兩次訊息慢速轉發:methodSignatureForSelector + resolveInstanceMethod
訊息的快速轉發是執行的forwardingTargetForSelector方法。
當慢速查詢,以及動態方法決議均沒有找到實現時,進行訊息轉發,首先是進行快速訊息轉發,即走到forwardingTargetForSelector方法
如果返回訊息接收者,在訊息接收者中還是沒有找到,則進入另一個方法的查詢流程
如果返回nil,則進入慢速訊息轉發
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
return [super forwardingTargetForSelector:aSelector];
}
針對第二次機會即快速轉發中還是沒有找到,則進入最後的一次挽救機會,即在Person中重寫methodSignatureForSelector,如下所示
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
// GM sayHello - anInvocation - 漂流瓶 - anInvocation
anInvocation.target = [LGStudent alloc];
// anInvocation 儲存 - 方法
[anInvocation invoke];
}
列印結果:
分析:
我們在resolveInstanceMethod的lookUpImpOrNil處打個斷點,檢視當前堆疊資訊
第一次執行動態方法決議時的堆疊是:
_objc_msgSend_uncached ==> lookUpImpOrForward ==> resolveInstanceMethod
第二次執行動態方法決議時的堆疊是:
methodSignatureForSelector > lookUpImpOrForward > resolveInstanceMethod
在堆疊資訊中,第二次執行動態方法決議時,是在執行訊息慢速轉發時執行的,並且是methodSignatureForSelector之後,forwardInvocation之前。
OC方法呼叫就是訊息傳送,那麼objc_msgSend流程分別是: