在OC底層探索11-objc_msgSend慢速查找流程中解釋了對(duì)方法的非緩存查詢以及方法查找失敗之后的系統(tǒng)報(bào)錯(cuò)阅签。
如果在2種機(jī)制下都沒(méi)有找到方法imp
,蘋果也給出了2條建議:
- 動(dòng)態(tài)方法決議:慢速查找流程未找到后蝎抽,會(huì)執(zhí)行一次動(dòng)態(tài)方法決議
resolveMethod_locked
- 消息轉(zhuǎn)發(fā):如果動(dòng)態(tài)方法決議仍然沒(méi)有找到實(shí)現(xiàn)政钟,則進(jìn)行消息轉(zhuǎn)發(fā)
1. 方法動(dòng)態(tài)決議
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 動(dòng)態(tài)方法決議 : 給一次機(jī)會(huì) 重新查詢
if (! cls->isMetaClass()) { // 對(duì)象 - 類
resolveInstanceMethod(inst, sel, cls);
}
else { // 類方法 - 元類
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
// 此處cls已經(jīng)是元類了。元類調(diào)用方法樟结,根據(jù)isa關(guān)系會(huì)在根元類中查找方法养交。
//通過(guò)根元類的繼承鏈最終找到NSObject,在NSObject中查詢`實(shí)例方法`的resolve的實(shí)現(xiàn)瓢宦。巧妙的設(shè)計(jì)K榱!
resolveInstanceMethod(inst, sel, cls);
}
}
// 在調(diào)用一次查詢
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
- 此處的cls已經(jīng)是指向
isa
的指針驮履,也就是類
,元類
,此邏輯是經(jīng)過(guò)匯編層的加工鱼辙。所以判斷不是元類就調(diào)用實(shí)例方法
的resolve實(shí)現(xiàn);反之調(diào)用類方法
的resolve實(shí)現(xiàn). - 根據(jù)元類的iSA關(guān)系廉嚼,最終會(huì)找到根元類(NSObject),因?yàn)轭惖母惗际?code>NSObject倒戏。但(NSObject)類中只存在對(duì)象方法怠噪,所以需要再調(diào)用一次
resolveInstanceMethod
, - 在方法動(dòng)態(tài)決議中杜跷,開(kāi)發(fā)者會(huì)重新實(shí)現(xiàn)該
sel
的imp
所以傍念, 需要重新進(jìn)行一次查詢。而且在本次查詢中會(huì)優(yōu)先在緩存中查找葱椭。
resolveInstanceMethod
實(shí)例方法的resolve具體源碼實(shí)現(xiàn)
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//生成動(dòng)態(tài)解析的方法Sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
//在當(dāng)前類的元類中查找resolve方法是否找得到
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
return;
}
//resolveInstanceMethod消息發(fā)送
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//進(jìn)行一次方法慢速查詢捂寿,將當(dāng)前方法插入緩存中口四,提高效率
IMP imp = lookUpImpOrNil(inst, sel, cls);
//在實(shí)現(xiàn)且需要打印是孵运,進(jìn)行打印
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 {
_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));
}
}
}
在
resolveInstanceMethod
調(diào)用后,又調(diào)用了一次lookUpImpOrNil
蔓彩;我們知道該方法如果找到對(duì)應(yīng)imp
之后會(huì)插入到對(duì)象類的緩存中去治笨,方便后續(xù)使用;另一個(gè)是方便debug進(jìn)行打印赤嚼。
如何打開(kāi)該打印
可以通過(guò)查看log來(lái)發(fā)現(xiàn)實(shí)現(xiàn)了resolveInstanceMethod
的方法
resolveClassMethod
類方法的resolve具體源碼實(shí)現(xiàn)
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//判斷resolveClassMethod方法是否實(shí)現(xiàn)
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
return;
}
//判斷類是否實(shí)現(xiàn)
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
//根據(jù)元類找到類
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//resolveClassMethod消息發(fā)送
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//進(jìn)行一次方法慢速查詢旷赖,將當(dāng)前方法插入緩存中,提高效率
IMP imp = lookUpImpOrNil(inst, sel, cls);
//在實(shí)現(xiàn)且需要打印是更卒,進(jìn)行打印
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 resolveClassMethod:%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));
}
}
}
- 實(shí)現(xiàn)邏輯于
resolveInstanceMethod
基本相同
使用方式
- 實(shí)例方法的resolve
為self動(dòng)態(tài)增加sayMaster
的實(shí)現(xiàn).使用runtime-api
+ (BOOL)resolveInstanceMethod:(SEL)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);
}
- 當(dāng)然也可以參考apple官方文檔
- 類方法的resolve
+ (BOOL)resolveClassMethod:(SEL)sel{
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);
}
- 唯一的區(qū)別就是類方法是需要添加到
元類
中的等孵,所以需要先找到元類objc_getMetaClass
.
根據(jù)觀察resolveInstanceMetho會(huì)走2次
-
第一次是在查詢方法時(shí)
lookupimp
中調(diào)用的
-
第二次是在coreFunction時(shí)調(diào)用的
- 在慢速轉(zhuǎn)發(fā)過(guò)程中會(huì)進(jìn)行第二次調(diào)用,后面會(huì)換種方式來(lái)驗(yàn)證
2.消息轉(zhuǎn)發(fā)
在之前有提到apple推薦的快速轉(zhuǎn)發(fā)
蹂空、慢速轉(zhuǎn)發(fā)
俯萌,他們是何時(shí)調(diào)用的呢?是以什么方式調(diào)用的呢上枕?現(xiàn)在就來(lái)討論下~
方法一
不知在之前有沒(méi)有留意一個(gè)方法log_and_fill_cache(...)
在這個(gè)方法中我們發(fā)現(xiàn)了一個(gè)系統(tǒng)提供的log
方法咐熙。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
- 是否打印就是看
objcMsgLogEnabled
這個(gè)參數(shù)的值,系統(tǒng)并沒(méi)有對(duì)外提供這個(gè)方法辨萍,但是我們可以自己導(dǎo)出.
調(diào)用方法
-
extern void instrumentObjcMessageSends(BOOL flag);
這個(gè)方法就是系統(tǒng)提供棋恼,但是需要我們手動(dòng)導(dǎo)出后使用的方法。 - 在我們調(diào)用方法的前后進(jìn)行锈玉,可以看到該方法的所有調(diào)用記錄
如何查看
結(jié)果
看到了熟悉的resolveInstanceMethod
爪飘,而且出現(xiàn)了2次,也印證了之前的猜測(cè)拉背。
與此同時(shí)還有些并不熟悉的方法forwardingTargetForSelector
,methodSignatureForSelector
师崎。
方法二
看到了這個(gè)調(diào)用的堆棧信息,調(diào)用的是CoreFoundation
庫(kù)∪ネ牛可是apple爸爸并沒(méi)有開(kāi)源這個(gè)庫(kù)抡诞,所以想要查看內(nèi)部的調(diào)用就需要拿出最終大招Hopper
反匯編穷蛹。
- 在lldb調(diào)試中使用
image list
查看CoreFoundation
的庫(kù)的本地地址。然后拖入Hopper
中昼汗。
-
forwarding在可以看到
forwardingTargetForSelector
的偽代碼調(diào)用 這就是所謂快速轉(zhuǎn)發(fā)流程
- 繼續(xù)跟流程會(huì)走到
methodSignatureForSelector
肴熏,以及forwardInvocation
這就是所謂慢速轉(zhuǎn)發(fā)流程
- 查看調(diào)用棧這兩個(gè)方法中間應(yīng)該會(huì)調(diào)用
resolveInstanceMethod
,但是在反匯編中沒(méi)有看到具體的調(diào)用,如果有知道的大佬可以提醒一下小弟顷窒。
消息轉(zhuǎn)發(fā)簡(jiǎn)單實(shí)現(xiàn)
// 1: 快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//此處返回一個(gè)實(shí)現(xiàn)該方法sel的對(duì)象
return [super forwardingTargetForSelector:aSelector];
}
// 2: 慢速轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//返回方法的參數(shù)編碼
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
//更換方法接受者
anInvocation.target = [LGStudent alloc];
//更換方法索引
anInvocation.selector = NSSelectorFromString(@"實(shí)現(xiàn)了該IMP的SEL");
//更換方法參數(shù)編碼
anInvocation.methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]
// anInvocation 保存 - 方法
[anInvocation invoke];
}
3. 整體流程圖
-
forwardInvocation
不處理并不會(huì)奔潰蛙吏。