上篇文章分析了慢速查找流程,若是遞歸完父類任然沒有找到imp
,就將imp = forward_imp
,由於c++
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
複製代碼
objc
源碼全局搜索_objc_msgForward_impcache
程序員
彙編中重點看一下
__objc_forward_handler
的實現。編程
全局搜索_objc_forward_handler
(注意去掉一個下劃線),來到c++
代碼:markdown
最終(消息轉發流程)找不到方法實現,報錯並打印報錯信息。 找不到方法最後報錯流程:
_objc_msgForward_impcache
->_objc_forward_handler
->_objc_fatal
函數
在慢速查找過程當中,若是沒有查找到imp
,而且沒有執行過動態方法決議就執行一次動態方法決議源碼分析
在慢速查找過程lookUpImpOrForward
中,執行了一次動態方法決議:post
//第一次:0011 & 0010 = 0010 (知足條件,只有一次機會)
//第二次:0000 & 0010 = 0000 (不知足條件)
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//0010 ^ 0010 = 0000 = behavior
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
複製代碼
resolveMethod_locked
定義:性能
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 程序員 你是否是傻 沒有這個方法 - imp-nil
// 奔潰 - 友善
// 給你一次機會 拯救地球 -> imp
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// lookUpImpOrForwardTryCache->_lookUpImpTryCache->_lookUpImpTryCache->lookUpImpOrForward
//再次慢速查找一次
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
複製代碼
對象方法決議resolveInstanceMethod
實現ui
static void resolveInstanceMethod(id inst, SEL sel, Class cls) {
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//若是cls中實現了resolveInstanceMethod
SEL resolve_sel = @selector(resolveInstanceMethod:);
///若是cls中沒有實現resolveInstanceMethod,系統默認也會有一個實現,因此下面不會return
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//系統會給cls中resolveInstanceMethod發送消息
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 = lookUpImpOrNilTryCache(inst, sel, cls);
省略部分代碼
}
複製代碼
對象方法決議:若是cls
中實現了resolveInstanceMethod
,系統會給resolveInstanceMethod
發送消息,若是沒有實現,系統也會有個resolveInstanceMethod
默認實現。 spa
1.準備調試代碼:
#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPerson *p = [ABPerson new];
[p doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
2.執行結果必然報錯: 3.按照對象方法決議源碼分析,修改
ABPerson
實現代碼
#import <objc/message.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
-(void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return [super resolveInstanceMethod:sel];
}
複製代碼
4.執行結果再也不報錯,而且成功將調用doSomething
轉爲調用saySomething
。
static void resolveClassMethod(id inst, SEL sel, Class cls) {
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//若是沒有實現resolveClassMethod,return,不過系統默認實現了一個,這裏不會return
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
//沒有實現元類報錯
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//系統會給nonmeta(元類)中resolveClassMethod發送消息
//nonmeta(元類)中的方法是以對象方法的形式存在的
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
省略部分代碼
}
複製代碼
類方法決議:系統會給nonmeta(元類)
中resolveClassMethod
發送消息,若是沒有實現,會給元類的父類(元類的父類的父類直到NSObject
爲止) 發消息。系統也會有個resolveClassMethod
默認實現。
1.準備調試代碼:
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
2.執行結果必然報錯:
3.按照對象方法決議源碼分析,修改ABPeople
實現代碼
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
+ (void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(objc_getMetaClass("ABPeople"), @selector(saySomething));
Method method = class_getInstanceMethod(objc_getMetaClass("ABPeople"), @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("ABPeople"), sel, sayImp, type);
}
return [super resolveClassMethod:sel];
}
@end
複製代碼
4.執行結果再也不報錯,而且成功將調用doSomething
轉爲調用saySomething
。
咱們知道了類方法是在元類中是以實力方法的方式存在的,元類的父類一直往上找是會找到NSObject
的,那麼NSObject
分類也是可以實現類方法決議的,類方法決議代碼修改:
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
//實力方法
- (void)doSomething;
//類方法
+ (void)doSomething;
@end
@implementation ABPeople
@end
@interface NSObject (Cate) @end @implementation NSObject (Cate) -(void)saySomething {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return NO;
}
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
仍然能夠成功運行。類別方式的決議一樣適用於實例方法
經過NSObject
類別的方式可以全局監聽方法找不到,經過 if (sel == @selector(doSomething))
,能夠將特定方法,如doSomething
進行後續的處理,如上傳日誌等。 這樣作就可以對原來的代碼無侵入,動態的注入代碼。可是這樣作也會帶來相應的性能消耗,如過多的if (sel == @selector(doSomething))
判斷,同時截斷了後面的消息轉發流程。 因此不建議在動態方法決議階段作AOP
。
instrumentObjcMessageSends
函數定義:
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);
//flag賦值給objcMsgLogEnabled
objcMsgLogEnabled = enable;
}
複製代碼
objcMsgLogEnabled
在logMessageSend
函數中的使用:
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector) {
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
//寫下日誌,路徑爲:"/tmp/msgSends-%d", (int) getpid ()
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
使用很簡單,只須要三步:
1.導出函數供外部使用:
extern void instrumentObjcMessageSends(BOOL flag);
複製代碼
2.在須要跟蹤的函數上下加上instrumentObjcMessageSends
,如:
ABPeople *p = [ABPeople new];
instrumentObjcMessageSends(YES);
[p doSomething];
instrumentObjcMessageSends(NO);
複製代碼
3.文件夾前往路徑:/tmp/msgSends
resolveInstanceMethod
(對象方法實現),resolveClassMethod
(類方法實現),經過此方法,能夠進行後續的處理,若是沒有實現將進入消息轉發流程。isa
繼承鏈,方法找不到,會一直找到NSObjct
,經過NSObjct
類別實現對象動態方法決議,能夠進行全局監聽,進行AOP
,但不建議在這個階段作AOP
。