前言
最近在搞重構(gòu)相關(guān)的事情,遇到了不少這樣的場(chǎng)景:
進(jìn)入一個(gè)界面,在viewWillAppear:的時(shí)候做相應(yīng)判斷,如果滿足條件則執(zhí)行對(duì)應(yīng)代碼锈嫩。
這類業(yè)務(wù)有一個(gè)特點(diǎn),業(yè)務(wù)內(nèi)容是對(duì)應(yīng)整個(gè)App的垦搬,與對(duì)應(yīng)的ViewController毛關(guān)系都沒(méi)有呼寸,但是卻不得不耦合到(即使是調(diào)用代碼可以精簡(jiǎn)到一行)ViewController中。
我們都知道悼沿,這種類似的業(yè)務(wù)用AOP(面向切片編程)來(lái)做十分適合等舔,所謂面向切片編程就是在不修改原方法的前提下,動(dòng)態(tài)的插入自己的想要的執(zhí)行代碼糟趾,由于Objective C是動(dòng)態(tài)語(yǔ)言慌植,可以很容易的利用method swizzling來(lái)實(shí)現(xiàn)AOP。
在正文之前特別感謝微信閱讀團(tuán)隊(duì)的這篇博客:
這篇博客原理上講解的比較清楚义郑,但是細(xì)節(jié)上并沒(méi)有講的很詳細(xì)蝶柿,所以也就有了本文。
Objective C方法調(diào)用過(guò)程
這個(gè)其實(shí)我之前在這篇博客里講過(guò):
iOS Runtime詳解(消息機(jī)制非驮,類元對(duì)象交汤,緩存機(jī)制,消息轉(zhuǎn)發(fā))
這里劫笙,把核心的內(nèi)容再一次列出來(lái)芙扎。
如下Objective C代碼
-?(NSInteger?)myTestFunction:(NSInteger)input{
????return?input?+?1;
}
-?(void)mySpecialFunction{
???NSInteger?result?=??[self?myTestFunction:10];
}
用clang來(lái)重寫為C++,
clang?-rewrite-objc??MyClass.m
然后,我們通過(guò)搜索mySpecialFunction方法名字填大,來(lái)找到轉(zhuǎn)換后的代碼戒洼,經(jīng)過(guò)簡(jiǎn)單整理如下
static?NSInteger?_I_MyClass_myTestFunction_(MyClass?*?self,?SEL?_cmd,?NSInteger?input)?{
????return?input?+?1;
}
static?void?_I_MyClass_mySpecialFunction(MyClass?*?self,?SEL?_cmd)?{
???NSInteger?result?=?objc_msgSend(self,?sel_registerName("myTestFunction:"),10);
}
我們看到,方法體進(jìn)行了如下轉(zhuǎn)換
//OC
-?(NSInteger?)myTestFunction:(NSInteger)input{
????return?input?+?1;
}
//C++
static?NSInteger?_I_MyClass_myTestFunction_(MyClass?*?self,?SEL?_cmd,?NSInteger?input)?{
????return?input?+?1;
}
方法調(diào)用進(jìn)行了如下轉(zhuǎn)換
//OC
NSInteger?result?=??[self?myTestFunction:10];
//C++
NSInteger?result?=?objc_msgSend(self,?sel_registerName("myTestFunction:"),10);
不難看出允华,方法的調(diào)用并不是直接轉(zhuǎn)換成了對(duì)應(yīng)的C/C++方法調(diào)用圈浇,而是調(diào)用了objc_msgSend通過(guò)SEL(就是一個(gè)字符串)在運(yùn)行時(shí)動(dòng)態(tài)找到這個(gè)的執(zhí)行體_I_MyClass_myTestFunction_。
那么靴寂,在運(yùn)行時(shí)如何找到這個(gè)方法的執(zhí)行體呢磷蜀? 這里省略一些細(xì)節(jié),對(duì)細(xì)節(jié)感興趣的同學(xué)可以看我上文寫的那篇文章百炬。一個(gè)實(shí)例方法的流程如下:
對(duì)象實(shí)例收到消息(SEL+參數(shù))
根據(jù)存儲(chǔ)在對(duì)象實(shí)例中的ISA到類對(duì)象褐隆,類對(duì)象依次查找Class Cache(方法表緩存)和dispatch table找到對(duì)應(yīng)的Method,如果找到Method剖踊,執(zhí)行對(duì)應(yīng)Method的IMP(方法體)妓灌,并且返回結(jié)果
如果找不到Method轨蛤,則根據(jù)類對(duì)象中的super_class指針找到父類的Class對(duì)象蜜宪。一直找到NSObject的類對(duì)象
如果NSObject也無(wú)法找到這個(gè)SEL虫埂,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制
如果消息轉(zhuǎn)發(fā)機(jī)制無(wú)法處理,則拋出異常: doesNotRecognizeSelector
Method Swizzling
通過(guò)上文我們知道圃验,一個(gè)方法的調(diào)用實(shí)際上就是SEL(方法名)通過(guò)Runtime找到IMP(方法執(zhí)行體)
既然是通過(guò)Runtime動(dòng)態(tài)找到的掉伏,那么我們就可以利用Runtime的API,講SEL_1來(lái)指向IMP_2澳窑,接著我們?cè)僭谠贗MP_2的方法體中執(zhí)行IMP_1斧散,就實(shí)現(xiàn)了動(dòng)態(tài)插入代碼。
消息轉(zhuǎn)發(fā)機(jī)制
在Objective C的方法調(diào)用過(guò)程中摊聋,我們提到了當(dāng)無(wú)法響應(yīng)一個(gè)selector時(shí)鸡捐,在拋出異常之前會(huì)先進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制。這里來(lái)詳細(xì)講解消息轉(zhuǎn)發(fā)的過(guò)程:
關(guān)于消息轉(zhuǎn)發(fā)麻裁,官方文檔在這里:Message Forwarding
在觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制即forwardInvocation:之前箍镜,Runtime提供了兩步來(lái)進(jìn)行輕量級(jí)的動(dòng)態(tài)處理這個(gè)selector.
resolveInstanceMethod:
Dynamically provides an implementation for a given selector for an instance method.
這個(gè)方法提供了一個(gè)機(jī)會(huì):為當(dāng)前類無(wú)法識(shí)別的SEL動(dòng)態(tài)增加IMP。
比如:最常見(jiàn)的可以通過(guò)class_addMethod
void?dynamicMethodIMP(id?self,?SEL?_cmd){/*...implementation...*/}
+?(BOOL)?resolveInstanceMethod:(SEL)aSEL
{
????if?(aSEL?==?@selector(resolveThisMethodDynamically))
????{
??????????class_addMethod([self?class],?aSEL,?(IMP)?dynamicMethodIMP,?"v@:");
??????????return?YES;
????}
????return?[super?resolveInstanceMethod:aSel];
}
Tips煎源,這里的"v@:"表示方法參數(shù)編碼色迂,v表示Void,@表示OC對(duì)象手销,:表示SEL類型歇僧。關(guān)于方法參數(shù)編碼,更詳細(xì)的內(nèi)容參見(jiàn)文檔锋拖。
如果resolveInstanceMethod返回NO诈悍,則表示無(wú)法在這一步動(dòng)態(tài)的添加方法,則進(jìn)入下一步:
forwardingTargetForSelector:
Returns?the?object?to?which?unrecognized?messages?should?first?be?directed.
這個(gè)方法提供了一個(gè)機(jī)會(huì):簡(jiǎn)單的把這個(gè)SEL交給另外一個(gè)對(duì)象來(lái)執(zhí)行兽埃。
比如:
-(id)forwardingTargetForSelector:(SEL)aSelector{
????if?(aSelector?==?@selector(dynamicSelector)?&&?[self.myObj?respondsToSelector:@selector(dynamicSelector)])?{
????????return?self.myObj;
????}else{
????????return?[super?forwardingTargetForSelector:aSelector];
????}
}
如果上述兩步都無(wú)法完成這個(gè)SEL的處理侥钳,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制,消息轉(zhuǎn)發(fā)機(jī)制有兩個(gè)比較重要的方法:
forwardInvocation: 具體的NSInvocaion
methodSignatureForSelector: 返回SEL的方法簽名
這里不得不提一下兩個(gè)類:
NSMethodSignature 用來(lái)表示方法的參數(shù)簽名信息:返回值讲仰,參數(shù)數(shù)量和類型
NSInvocaion SEL + 執(zhí)行SEL的Target + 參數(shù)值
通常慕趴,拿到NSInvocaion對(duì)象后,我們可選擇的進(jìn)行如下操作
修改執(zhí)行的SEL
修改執(zhí)行的Target
修改傳入的參數(shù)
然后調(diào)用:[invocation invoke]鄙陡,來(lái)執(zhí)行這個(gè)消息冕房。
_objc_msgForward
我們知道,正常情況下SEL背后會(huì)對(duì)一個(gè)IMP趁矾,在OC中有一個(gè)特殊的IMP就是:_objc_msgForward耙册。當(dāng)執(zhí)行_objc_msgForward時(shí),會(huì)直接觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制毫捣,即forwardInvocation:详拙。
Aspect的基本原理
使用Aspect帝际,可以在一個(gè)OC方法執(zhí)行前/后插入代碼,也可以替換這個(gè)OC方法的實(shí)現(xiàn)饶辙。
這里蹲诀,我們以在ViewControler的viewWillAppear:方法之后插入一段代碼為例,來(lái)講解hook前后的變化弃揽,
在沒(méi)有hook之前脯爪,ViewController的SEL與IMP關(guān)系如下
調(diào)用以下aspect來(lái)hook viewWillAppear:后:
?[ViewController?aspect_hookSelector:@selector(viewWillAppear:)
????????????????????????????withOptions:AspectPositionAfter
?????????????????????????????usingBlock:^{
?????????????????????????????????NSLog(@"Insert?some?code?after?ViewWillAppear");
?????????????????????????????}?error:&error];
最初的viewWillAppear: 指向了_objc_msgForward
增加了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP
最初的forwardInvocation:指向了Aspect提供的一個(gè)C方法__ASPECTS_ARE_BEING_CALLED__
動(dòng)態(tài)增加了__aspects_forwardInvocation:,指向最初的forwardInvocation:的IMP
然后,我們?cè)賮?lái)看看hook后矿微,一個(gè)viewWillAppear:的實(shí)際調(diào)用順序:
object收到selector(viewWillAppear:)的消息
找到對(duì)應(yīng)的IMP:_objc_msgForward痕慢,執(zhí)行后觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制。
object收到forwardInvocation:消息
找到對(duì)應(yīng)的IMP:__ASPECTS_ARE_BEING_CALLED__涌矢,執(zhí)行IMP
向object對(duì)象發(fā)送aspects_viewWillAppear:,執(zhí)行最初的viewWillAppear方法的IMP
????????????執(zhí)行插入的block代碼
????????????如果ViewController無(wú)法響應(yīng)aspects_viewWillAppear掖举,則向object對(duì)象發(fā)送__aspects_forwardInvocation:來(lái)執(zhí)行最初的forwardInvocation IMP
????????????所以,Aspects是采用了集中式的hook方式娜庇,所有的調(diào)用最后走的都是一個(gè)C函數(shù)__ASPECTS_ARE_BEING_CALLED__塔次。
核心類/數(shù)據(jù)結(jié)構(gòu)
AspectIdentifier - 代表一個(gè)Aspect的具體信息:包括被Hook的對(duì)象,SEL思灌,插入的block等具體信息俺叭。
@interface?AspectIdentifier?:?NSObject
@property?(nonatomic,?assign)?SEL?selector;
@property?(nonatomic,?strong)?id?block;
@property?(nonatomic,?strong)?NSMethodSignature?*blockSignature;
@property?(nonatomic,?weak)?id?object;
@property?(nonatomic,?assign)?AspectOptions?options;
@end
AspectTracker - 跟蹤一個(gè)類的繼承鏈中的hook狀態(tài):包括被hook的類,哪些SEL被hook了泰偿。
@interface?AspectTracker?:?NSObject
@property?(nonatomic,?strong)?Class?trackedClass;
@property?(nonatomic,?readonly)?NSString?*trackedClassName;
@property?(nonatomic,?strong)?NSMutableSet?*selectorNames;
@property?(nonatomic,?strong)?NSMutableDictionary?*selectorNamesToSubclassTrackers;
@end
AspectContainer - AspectIdentifier的容器:以SEL合成key熄守,然后作為關(guān)聯(lián)對(duì)象存儲(chǔ)到對(duì)應(yīng)的類/對(duì)象里。包括beforeAspects耗跛,insteadAspects裕照,afterAspects
@interface?AspectsContainer?:?NSObject
@property?(atomic,?copy)?NSArray?*beforeAspects;
@property?(atomic,?copy)?NSArray?*insteadAspects;
@property?(atomic,?copy)?NSArray?*afterAspects;
@end
AspectInfo - NSInvocation的容器,表示一個(gè)執(zhí)行的Command调塌。
@interface?AspectInfo?:?NSObject
@property?(nonatomic,?unsafe_unretained,?readonly)?id?instance;
@property?(nonatomic,?strong,?readonly)?NSArray?*arguments;
@property?(nonatomic,?strong,?readonly)?NSInvocation?*originalInvocation;
@end
hook過(guò)程
同樣晋南,我們以一個(gè)實(shí)例方法為例,講解在這個(gè)方法調(diào)用后發(fā)生了什么
?[ViewController?aspect_hookSelector:@selector(viewWillAppear:)
????????????????????????????withOptions:AspectPositionAfter
?????????????????????????????usingBlock:^{
?????????????????????????????????NSLog(@"Insert?some?code?after?ViewWillAppear");
?????????????????????????????}?error:&error];
1.對(duì)Class和MetaClass進(jìn)行進(jìn)行合法性檢查羔砾,判斷能否hook负间,規(guī)則如下
????????????retain,release,autorelease,forwoardInvocation:不能被hook
????????????dealloc只能在方法前hook
????????????類的繼承關(guān)系中,同一個(gè)方法只能被hook一次
2.創(chuàng)建AspectsContainer對(duì)象姜凄,以aspects_ + SEL為key政溃,作為關(guān)聯(lián)對(duì)象依附到被hook 的對(duì)象上
objc_setAssociatedObject(self,?aliasSelector,?aspectContainer,?OBJC_ASSOCIATION_RETAIN);
3.創(chuàng)建AspectIdentifier對(duì)象,并且添加到AspectsContainer對(duì)象里存儲(chǔ)起來(lái)态秧。這個(gè)過(guò)程分為兩步?
????????????生成block的方法簽名NSMethodSignature
????????????對(duì)比block的方法簽名和待hook的方法簽名是否兼容(參數(shù)個(gè)數(shù)董虱,按照順序的類型)
4.根據(jù)hook實(shí)例對(duì)象/類對(duì)象/類元對(duì)象的方法做不同處理。其中,對(duì)于上文以類方法來(lái)hook的時(shí)候愤诱,分為兩步
hook類對(duì)象的forwoardInvocation:方法云头,指向一個(gè)靜態(tài)的C方法,并且創(chuàng)建一個(gè)aspects_ forwoardInvocation:動(dòng)態(tài)添加到之前的類中
IMP?originalImplementation?=?class_replaceMethod(klass,?@selector(forwardInvocation:),?(IMP)__ASPECTS_ARE_BEING_CALLED__,?"v@:@");
if?(originalImplementation)?{
????class_addMethod(klass,?NSSelectorFromString(AspectsForwardInvocationSelectorName),?originalImplementation,?"v@:@");
}
hook類對(duì)象的viewWillAppear:方法讓其指向_objc_msgForward,動(dòng)態(tài)添加aspects_viewWillAppear:指向最初的viewWillAppear:實(shí)現(xiàn)
Hook實(shí)例的方法
Aspects支持只hook一個(gè)對(duì)象的實(shí)例方法
只不過(guò)在第4步略有出入淫半,當(dāng)hook一個(gè)對(duì)象的實(shí)例方法的時(shí)候:
新建一個(gè)子類溃槐,_Aspects_ViewController,并且按照上述的方式hook forwoardInvocation:
hook _Aspects_ViewController的class方法,讓其返回ViewController
hook 子類的類元對(duì)象撮慨,讓其返回ViewController
調(diào)用objc_setClass來(lái)修改ViewController的類為_(kāi)Aspects_ViewController
這樣做竿痰,就可以通過(guò)object_getClass(self)獲得類名,然后看看是否有前綴類名來(lái)判斷是否被hook過(guò)了
其他
object_getClass/與self.class的區(qū)別
object_getClass獲得的是isa的指向
self.class則不一樣砌溺,當(dāng)self是實(shí)例對(duì)象的時(shí)候,返回的是類對(duì)象变隔,否則則返回自身规伐。
比如:
TestClass?*?testObj?=?[[TestClass?alloc]?init];
//Same
logAddress([testObj?class]);
logAddress([TestClass?class]);
//Not?same
logAddress(object_getClass(testObj));
logAddress(object_getClass([TestClass?class]));
Log
2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930
2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930
2017-05-22?22:41:48.216?OCTest[899:25934]?0x107d10930
2017-05-22?22:41:49.061?OCTest[899:25934]?0x107d10908
Block簽名
block因?yàn)楸澈笃鋵?shí)是一個(gè)C結(jié)構(gòu)體,結(jié)構(gòu)體中存儲(chǔ)著著一個(gè)函數(shù)指針來(lái)指向?qū)嶋H的方法體
Block的內(nèi)存布局如下
typedef?NS_OPTIONS(int,?AspectBlockFlags)?{
????AspectBlockFlagsHasCopyDisposeHelpers?=?(1?<<?25),
????AspectBlockFlagsHasSignature??????????=?(1?<<?30)
};
typedef?struct?_AspectBlock?{
????__unused?Class?isa;
????AspectBlockFlags?flags;
????__unused?int?reserved;
????void?(__unused?*invoke)(struct?_AspectBlock?*block,?...);
????struct?{
????????unsigned?long?int?reserved;
????????unsigned?long?int?size;
????????//?requires?AspectBlockFlagsHasCopyDisposeHelpers
????????void?(*copy)(void?*dst,?const?void?*src);
????????void?(*dispose)(const?void?*);
????????//?requires?AspectBlockFlagsHasSignature
????????const?char?*signature;
????????const?char?*layout;
????}?*descriptor;
????//?imported?variables
}?*AspectBlockRef;
對(duì)應(yīng)生成NSMethodSignature的方法:
static?NSMethodSignature?*aspect_blockMethodSignature(id?block,?NSError?**error)?{
????AspectBlockRef?layout?=?(__bridge?void?*)block;
????if?(!(layout->flags?&?AspectBlockFlagsHasSignature))?{
????????NSString?*description?=?[NSString?stringWithFormat:@"The?block?%@?doesn't?contain?a?type?signature.",?block];
????????AspectError(AspectErrorMissingBlockSignature,?description);
????????return?nil;
????}
????void?*desc?=?layout->descriptor;
????desc?+=?2?*?sizeof(unsigned?long?int);
????if?(layout->flags?&?AspectBlockFlagsHasCopyDisposeHelpers)?{
????????desc?+=?2?*?sizeof(void?*);
????}
????if?(!desc)?{
????????NSString?*description?=?[NSString?stringWithFormat:@"The?block?%@?doesn't?has?a?type?signature.",?block];
????????AspectError(AspectErrorMissingBlockSignature,?description);
????????return?nil;
????}
????const?char?*signature?=?(*(const?char?**)desc);
????return?[NSMethodSignature?signatureWithObjCTypes:signature];
}
關(guān)于Block的更多講解匣缘,參見(jiàn)我的前一篇博客
效率
消息轉(zhuǎn)發(fā)機(jī)制相對(duì)于正常的方法調(diào)用來(lái)說(shuō)是比較昂貴的猖闪,所以一定不要用消息轉(zhuǎn)發(fā)機(jī)制來(lái)處理那些一秒鐘成百上千次的調(diào)用。
總結(jié)
Objective C的消息轉(zhuǎn)發(fā)機(jī)制是一個(gè)非常靈活的機(jī)制肌厨,用好它會(huì)讓你實(shí)現(xiàn)很多黑科技培慌,也能夠讓你的架構(gòu)更加靈活
原文: