上篇文章分析了 消息慢速查找 流程,當(dāng)消息找不到的時候會執(zhí)行_objc_msgForward_impcache匯編代碼。最終調(diào)用到_objc_forward_handler進(jìn)行報錯處理,那么在報錯之前能夠進(jìn)行處理么?
一、動態(tài)方法決議
當(dāng)imp沒有找到的時候的時候會賦值libobjc.A.dylib_objc_msgForward_impcache`,首先會進(jìn)入如下代碼邏輯:
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
//要查找的對象,方法,類,1
return resolveMethod_locked(inst, sel, cls, behavior);
}
- 這其實(shí)可以理解為一個單類,相同流程只會進(jìn)入一次。
-
behavior上篇文章已經(jīng)分析,值中有LOOKUP_INITIALIZE|LOOKUP_RESOLVER進(jìn)入后異或LOOKUP_INITIALIZE|LOOKUP_RESOLVER^ LOOKUP_RESOLVER=LOOKUP_INITIALIZE,相當(dāng)于清空了LOOKUP_RESOLVER。 -
resolveMethod_locked參數(shù)最后一個是LOOKUP_INITIALIZE。
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()) {
//這里的cls是類
resolveInstanceMethod(inst, sel, cls);
}
else {
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
//這里的cls是元類
resolveInstanceMethod(inst, sel, cls);
}
}
//又會去查找一次,既然這里又會去查找一次,那么肯定有什么地方會加入之前查找不存在的方法。
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 當(dāng)快速和慢速消息查找都沒有找到的時候進(jìn)入了
resolveMethod_locked。 - 查找的是實(shí)例方法則進(jìn)行對象方法動態(tài)決議
resolveInstanceMethod。 - 查找的是類方法則先進(jìn)行類方法動態(tài)決議
resolveClassMethod,再執(zhí)行resolveInstanceMethod(這里resolveInstanceMethod調(diào)用與實(shí)例方法的resolveInstanceMethod參數(shù)不同。)。 - 最后會調(diào)用
lookUpImpOrForwardTryCache查找。
核心問題是最后要返回imp,那么先看下lookUpImpOrForwardTryCache進(jìn)行的操作:
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
只是一個簡單的調(diào)用,繼續(xù)排查:
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//是否初始化,正常情況下是已經(jīng)初始化了。
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
//這就是慢速消息查找流程,與之前的區(qū)別是 behavior = LOOKUP_INITIALIZE,沒有動態(tài)方法決議參數(shù)了。
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//緩存查找
IMP imp = cache_getImp(cls, sel);
//找到直接跳轉(zhuǎn)done
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
//動態(tài)共享緩存查找
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
//imp不存在繼續(xù)慢速消息查找流程
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//是否消息轉(zhuǎn)發(fā)
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
//返回imp
return imp;
}
-
isInitialized正常情況是不會進(jìn)入的。 - 先去緩存查找對應(yīng)的
imp,找到直接返回。 - 沒有找到會去動態(tài)共享緩存查找(如果支持)。
- 仍然沒有會進(jìn)行
lookUpImpOrForward也就是再進(jìn)行一次慢速消息查找。
既然這個函數(shù)也是進(jìn)行快速和慢速消息查找的,那么就說明resolveInstanceMethod與resolveClassMethod可以在某個時機(jī)將方法加入類中。這樣后面方法的調(diào)用才有意義。
二、對象方法動態(tài)決議 resolveInstanceMethod
通過源碼分析發(fā)現(xiàn)在進(jìn)行了快速與慢速消息查找后如果找不到imp,蘋果仍然給了機(jī)會進(jìn)行resolveInstanceMethod處理,那么核心肯定是要給類中添加imp,源碼如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//先進(jìn)行元類查找是否實(shí)現(xiàn)了`resolveInstanceMethod`實(shí)例方法,也就是類的類方法。沒有實(shí)現(xiàn)直接返回,這里不會返回,因?yàn)镹Sobject默認(rèn)實(shí)現(xiàn)了。
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//系統(tǒng)自動發(fā)送了`resolveInstanceMethod`消息,由于消息的接受者是類,所以是+方法。
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);
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));
}
}
}
- 先進(jìn)行元類
resolveInstanceMethod的查找和緩存。 - 系統(tǒng)自動給類發(fā)送了
resolveInstanceMethod消息。既然是類調(diào)用的,那么就是+方法。 - 接著進(jìn)行了快速慢速方法查找
imp,但是沒有返回imp(為什么不返回?這里只是緩存,如果有的話)。 -
lookUpImpOrNilTryCache與lookUpImpOrForwardTryCache唯一的區(qū)別是是否進(jìn)行動態(tài)轉(zhuǎn)發(fā)。這里不進(jìn)行動態(tài)轉(zhuǎn)發(fā)。 - 可以看到返回的
resolved只是進(jìn)行了日志打印。也就是resolved返回YES/NO對功能沒有影響。
那么就有個問題?
既然查找了imp為什么不進(jìn)行返回操作?而resolveInstanceMethod調(diào)用結(jié)束后還查了一次?
2.1 + (BOOL)resolveInstanceMethod 調(diào)試分析
resolveInstanceMethod源碼跟蹤流程如下:
-
cls->ISA元類也就是HPObject元類中查找有沒有實(shí)現(xiàn)resolveInstanceMethod-imp,最終會找到NSObject元類然后將resolveInstanceMethod-imp緩存寫入HPObject元類的緩存。(NSObject默認(rèn)實(shí)現(xiàn)了)。 - 給類發(fā)送
resolveInstanceMethod消息。 -
HPObject查找instanceMethod有沒有實(shí)現(xiàn),沒有實(shí)現(xiàn)會將instanceMethod-_objc_msgForward_impcache(IMP)寫入HPObject緩存。這個時候由于LOOKUP_NIL的存在返回的是`nil。 - 如果
lookUpImpOrNilTryCache沒有找到imp會返回繼續(xù)執(zhí)行lookUpImpOrForwardTryCache繼續(xù)進(jìn)行緩存->消息慢速查找流程(消息慢速查找不會執(zhí)行)。因?yàn)榍懊嬉呀?jīng)寫入了對應(yīng)緩存。這次會從緩存中獲取到imp為_objc_msgForward_impcache的imp。不會進(jìn)入消息慢速查找流程,直接進(jìn)行了消息轉(zhuǎn)發(fā)。
這就說明resolveInstanceMethod中首先元類查找resolveInstanceMethod,目的是將resolveInstanceMethod寫入緩存。然后類發(fā)送resolveInstanceMethod消息。接著lookUpImpOrNilTryCache調(diào)用是將的imp加入緩存中(無論是否找到,找不到會存入_objc_msgForward_impcache)。返回后lookUpImpOrForwardTryCache從緩存中找方法返回。
結(jié)論:resolveInstanceMethod中l(wèi)ookUpImpOrNilTryCache只是將方法插入緩存,返回后lookUpImpOrForwardTryCache從緩存中獲取imp 這也是調(diào)用兩次的原因。
2.2 + (BOOL)resolveInstanceMethod 實(shí)現(xiàn)
既然系統(tǒng)已經(jīng)給了+ (BOOL)resolveInstanceMethod:(SEL)sel進(jìn)行容錯處理,那么就實(shí)現(xiàn)下:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
調(diào)用后發(fā)現(xiàn)這個方法調(diào)用了兩次:
resolveInstanceMethod: HPObject-instanceMethod
resolveInstanceMethod: HPObject-instanceMethod
- 在
HPObject的元類中能找到resolveInstanceMethod方法,緩存的直接是自己的imp了。 - 仍然是沒有命中進(jìn)行了消息轉(zhuǎn)發(fā)。
消息轉(zhuǎn)發(fā)會進(jìn)入class_getInstanceMethod:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
又進(jìn)行了一次lookUpImpOrForward所以這也是調(diào)用了兩次的原因。但是這次不進(jìn)行消息轉(zhuǎn)發(fā)了,所以不會造成死循環(huán)。
總結(jié):第一次沒有命中后,再進(jìn)行消息轉(zhuǎn)發(fā)后又會進(jìn)行一次lookUpImpOrForward消息慢速查找流程,所以resolveInstanceMethod會執(zhí)行兩次。
那么如果實(shí)現(xiàn)中添加了imp就肯定只調(diào)用一次了。
修改代碼如下:
- (void)instanceMethod1 {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"HPObject resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(instanceMethod)) {
IMP instanceMethod1 = class_getMethodImplementation(self, @selector(instanceMethod1));
Method method = class_getInstanceMethod(self, @selector(instanceMethod1));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, instanceMethod1, type);
}
return NO;
}
按照源碼理解在lookUpImpOrNilTryCache調(diào)用中只是增加到了緩存中,后面lookUpImpOrForwardTryCache會從緩存中查找,找到imp然后執(zhí)行。
+ (BOOL)resolveInstanceMethod:(SEL)sel返回NO/YES根據(jù)源碼來看只是打印日志相關(guān)的內(nèi)容,應(yīng)該是沒有影響的。經(jīng)過調(diào)試驗(yàn)證確實(shí)沒有影響。
結(jié)論:
- resolveInstanceMethod 調(diào)用中只是對方法的緩存,lookUpImpOrNilTryCache 從緩存中再次查找方法。這也是為什么會查找兩次的原因。
- resolveInstanceMethod 執(zhí)行兩次的原因是,在方法沒有命中的時候消息轉(zhuǎn)發(fā)過程中會再次進(jìn)行l(wèi)ookUpImpOrForward(消息慢速查找),這就是執(zhí)行兩次的原因。
- + (BOOL)resolveInstanceMethod:(SEL)sel 返回值不會影響功能,只是對日志打印有影響,并且默認(rèn)情況下是不打印日志的。
三、類方法動態(tài)決議resolveClassMethod
在上面最開始分析的時候類方法動態(tài)決議會先調(diào)用resolveClassMethod,如果沒有命中那么就會調(diào)用resolveInstanceMethod:
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
resolveClassMethod的實(shí)現(xiàn)如下:
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//不會進(jìn)入這里,先查找元類是否實(shí)現(xiàn)`resolveClassMethod`
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//類方法存在元類中,操作元類防止沒有實(shí)現(xiàn)。
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);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//非元類調(diào)用,也就是類方法
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);
if (resolved && PrintResolving) {//......}
}
- 第一個
lookUpImpOrNilTryCache查找元類是否實(shí)現(xiàn),先將resolveClassMethod插入HPObject元類的緩存中。resolveClassMethod。NSObject默認(rèn)實(shí)現(xiàn)了。 - 操作元類,防止元類沒有實(shí)現(xiàn)。
- 元類中是以對象方法存在,所以在類中實(shí)現(xiàn)類方法就可以了。系統(tǒng)主動給類方法發(fā)送
+ resolveClassMethod消息。這里細(xì)節(jié)的一點(diǎn)是通過nonmeta來發(fā)送消息。 -
lookUpImpOrNilTryCache查找目標(biāo)imp,先緩存后慢速。查找到后將imp插入緩存,沒有找到則將_objc_msgForward_impcache插入緩存。
實(shí)現(xiàn)如下:
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
調(diào)用后發(fā)現(xiàn)打印了8次:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
- 其中
encodeWithOSLogCoder與我們無關(guān),classMethod出現(xiàn)兩次符合預(yù)期(另外一次消息轉(zhuǎn)發(fā)過程中調(diào)用)。 - 當(dāng)調(diào)用
resolveClassMethod沒有實(shí)現(xiàn)的時候,就調(diào)用resolveInstanceMethod去查找(這里的cls參數(shù)是元類,與查找實(shí)例方法不同),仍然沒有找到就執(zhí)行lookUpImpOrForwardTryCache。 - 最后在消息轉(zhuǎn)發(fā)的時候會再執(zhí)行一次方法動態(tài)決議。
修改實(shí)現(xiàn):
+ (void)classMethod1 {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(classMethod)) {
IMP classMethod1 = class_getMethodImplementation(objc_getMetaClass("HPObject"), @selector(classMethod1));
Method method = class_getClassMethod(self, @selector(classMethod1));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("HPObject"), sel, classMethod1, type);
}
return [super resolveClassMethod:sel];
}
輸出:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: HPObject-classMethod
+[HPObject classMethod1]
這個時候就調(diào)用一次了。
既然resolveClassMethod找不到的時候會執(zhí)行一次resolveInstanceMethod,那意味者可以在resolveInstanceMethod中對類方法進(jìn)行處理。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"HPObject resolveInstanceMethod: %@-%@",self,NSStringFromSelector(sel));
return NO;
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"HPObject resolveClassMethod: %@-%@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
這個時候調(diào)試發(fā)現(xiàn)resolveInstanceMethod并沒有執(zhí)行。為什么?因?yàn)檫@里是HPObject元類調(diào)用resolveInstanceMethod。
根據(jù)isa的走位圖,NSObject同時也是元類,那么元類調(diào)用+方法就要存到元類的元類中也就是存在根元類的元類,那么就是NSObject自己,通過NSObject的resolveInstanceMethod方法就可以實(shí)現(xiàn)了。
添加一個NSObject的分類,實(shí)現(xiàn)方法:
- (void)instanceMethod1 {
NSLog(@"%s",__func__);
}
+ (void)classMethod1 {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod: %@-%p-%@",self,self,NSStringFromSelector(sel));
if (sel == @selector(instanceMethod)) {
IMP instanceMethod1 = class_getMethodImplementation(self, @selector(instanceMethod1));
Method method = class_getInstanceMethod(self, @selector(instanceMethod1));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, instanceMethod1, type);
} else if (sel == @selector(classMethod)) {
IMP classMethod1 = class_getMethodImplementation(objc_getMetaClass("HPObject"), @selector(classMethod1));
Method method = class_getInstanceMethod(objc_getMetaClass("HPObject"), @selector(classMethod1));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("HPObject"), sel, classMethod1, type);
}
return NO;
}
分別調(diào)用instanceMethod和classMethod輸出如下:
HPObject:0x1000082c8, HPMetaObject:0x1000082a0, NSObject:0x100358140, NSMetaObject:0x1003580f0
resolveInstanceMethod: HPObject-0x1000082c8-instanceMethod //類
-[NSObject(Additions) instanceMethod1]
resolveInstanceMethod: HPObject-0x1000082a0-classMethod //元類
HPObjcTest[59242:11857560] +[NSObject(Additions) classMethod1]
這樣就在NSObject的resolveInstanceMethod中即處理了類方法也處理了實(shí)例方法。兩次調(diào)用參數(shù)不同,一次是類調(diào)用,一次是元類調(diào)用。
??如果兩個都實(shí)現(xiàn)在
HPObject類中,則都是類調(diào)用。
總結(jié):
- resolveClassMethod 調(diào)用中只是對方法的緩存,lookUpImpOrNilTryCache會從緩存中再次查找方法,這也是為什么會查找兩次的原因。
-
resolveClassMethod 執(zhí)行兩次的原因是在方法沒有命中的時候消息轉(zhuǎn)發(fā)過程中會再次進(jìn)行l(wèi)ookUpImpOrForward(消息慢速查找),再次走這個流程。這就是執(zhí)行兩次的原因。(
LOOKUP_NIL有值,所以不會再次消息轉(zhuǎn)發(fā),不會造成死循環(huán)。) - resolveClassMethod 沒有命中的時候會先調(diào)用resolveInstanceMethod(這里的
cls是元類),再次調(diào)用時因?yàn)?code>NSObject是元類的父類。 - 這里resolveInstanceMethod由于是元類調(diào)用,所以只能實(shí)現(xiàn)在NSObject的分類中。(根元類的元類是自己,它的父類是
NSObject) - + (BOOL)resolveClassMethod:(SEL)sel 返回值不會影響功能,只是對日志打印有影響。
三、aop & oop
那么動態(tài)方法決議的意義在哪里呢?
這是蘋果在sel查找imp找不到的時候給的一次解決錯誤的機(jī)會。有什么意義呢?在NSObject的分類中,所有找不到的OC方法都能在resolveInstanceMethod中監(jiān)聽到。
那么在自己的工程中可以根據(jù)類名前綴、模塊以及事物進(jìn)行區(qū)分prefix_ module_traffic。當(dāng)發(fā)現(xiàn)有問題的時候可以進(jìn)行容錯處理并且上報錯誤信息。 比如HP_Setting_didClickLogin出現(xiàn)問題的時候進(jìn)行上報,當(dāng)超過閾值時進(jìn)行報警。
這種方式就是aop切面編程。我們比較習(xí)慣的方式是oop。
oop
oop分工非常明確,耦合度小,冗余代碼。一般情況下會提取公共的類,但是遵循后會對它有強(qiáng)依賴,強(qiáng)耦合。
這些其實(shí)不是我們關(guān)心的,我們更關(guān)心業(yè)務(wù)的內(nèi)容,所以公共類盡量少侵入,最好無侵入。通過動態(tài)方式注入代碼,對原始方法沒有影響。這就相當(dāng)于整個切面切入了,要切入的方法和類就是切點(diǎn)。aop是oop的延伸。
aop
aop的缺點(diǎn)在上面的例子中是if-else過多冗余。正如上面看到的那樣,方法會調(diào)用很多次浪費(fèi)了相應(yīng)的性能。如果命中還好,沒有命中會走多次,會有性能消耗。它是消息轉(zhuǎn)發(fā)機(jī)制的前一個階段。意味著如果在這里做了容錯處理,后面的流程就被切掉了。蘋果寫轉(zhuǎn)發(fā)流程就沒有意義了。
如果其它模塊也做了相應(yīng)處理,重復(fù)了這塊不一定會執(zhí)行到。所以在后面的流程做aop更合理。
四、消息轉(zhuǎn)發(fā)流程
如果最終動態(tài)方法決議也沒有找到imp呢?動態(tài)方法決議會返回imp,這個時候的imp是指向_objc_msgForward_impcache的。
那么這個時候后面的流程怎么執(zhí)行呢?
可以通過聲明一個函數(shù)instrumentObjcMessageSends打印系統(tǒng)調(diào)用的方法的列表,調(diào)用和聲明方式如下:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPObject *obj = [HPObject alloc];
instrumentObjcMessageSends(YES);
[obj instanceMethod];
instrumentObjcMessageSends(NO);
}
return 0;
}
在源碼中它的實(shí)現(xiàn)如下:
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;
}
作用是什么呢?
搜索objcMsgLogEnabled會發(fā)現(xiàn)在開啟的情況下會在/tmp/msgSends-%d中寫下日志:

調(diào)用輸出結(jié)果如下:

可以看到調(diào)用了非常多的方法,其中
resolveInstanceMethod已經(jīng)是熟悉的了。其它的是消息轉(zhuǎn)發(fā)流程的方法了。這里很遺憾的是看不到參數(shù)。
- 根據(jù)日志可以看到在
methodSignatureForSelector后再次進(jìn)行了resolveInstanceMethod。 - 根據(jù)源碼分析可知,應(yīng)該是有兩個方法被調(diào)用。
- 在動態(tài)方法決議后消息轉(zhuǎn)發(fā)流程包含方法:
forwardingTargetForSelector:
methodSignatureForSelector:
doesNotRecognizeSelector:
那么在源碼中調(diào)用跟蹤下參數(shù)呢?
既然都是調(diào)用的NSObject的方法不防在NSObject里面打斷點(diǎn),根據(jù)之前的調(diào)試也能判斷出來應(yīng)該是
encodeWithOSLogCoder:options:maxLength
驗(yàn)證確實(shí)是:

消息轉(zhuǎn)發(fā)整個流程將在下篇文章詳細(xì)分析。
動態(tài)方法決議整個流程圖:
