一、面向切面編程應用在統計上 業務邏輯和統計邏輯常常耦合在一塊兒,一方面影響了正常的業務邏輯,同時也很容易搞亂打點邏輯,並且要查看打點狀況的時候也很分散。在 web 編程時候,這種場景很早就有了很成熟的方案,也就是所謂的AOP 編程(面向切面編程),其原理也就是在不更改正常的業務處理流程的前提下,經過生成一個動態代理類,從而實現對目標對象嵌入附加的操做。在 iOS 中,要想實現類似的效果也很簡單,利用 oc 的動態性,經過 swizzling method 改變目標函數的 selector 所指向的實現,而後在新的實現中實現附加的操做,完成以後再回到原來的處理邏輯。 開源框架Aspects是一個很是好的框架。 Aspects git
二、基本原理github
每個對象都有一個指向其所屬類的isa指針,經過該指針找到所屬的類,而後會在所屬類中的方法列表中尋找方法的實現,若是在方法列表中查到了和選擇子名稱相符的方法就會跳轉到他的方法實現,若是找不到會向其父類的方法列表中查找,以此類推,直到NSObject類,若是仍是查找不到就會執行「消息轉發」操做。 另外爲了保證消息機制的效率,每個類都設置一個緩存方法列表,緩存列表中包含了當前類的方法以及繼承自父類的方法,在查詢方法列的時候,都會先查詢本類的緩存列表,再去查詢方法類別。這樣當一個方法已經被調用過一次,下次調用就會很快的查詢到並調用。web
方法調用的過程
1.在對象本身緩存的方法列表中去找要調用的方法,找到了就直接執行其實現。
2.緩存裏沒找到,就去上面說的它的方法列表裏找,找到了就執行其實現。
3.還沒找到,說明這個類本身沒有了,就會經過isa去向其父類裏執行一、2。
4.若是找到了根類還沒找到,那麼就是沒有了,會轉向一個攔截調用的方法,咱們能夠本身在攔截調用方法裏面作一些處理。
5.若是沒有在攔截調用裏作處理,那麼就會報錯崩潰。
複製代碼
從上面咱們能夠發現,在發消息的時候,若是 selector 有對應的 IMP,則直接執行,若是沒有就進行查找,若是最後沒有查找到。OC 給咱們提供了幾個可供補救的機會,依次有 resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation。編程
Aspects 之因此選擇在 forwardInvocation 這裏處理是由於,這幾個階段特性都不太同樣:緩存
resolvedInstanceMethod 適合給類/對象動態添加一個相應的實現forwardingTargetForSelector 適合將消息轉發給其餘對象處理 forwardInvocation 是裏面最靈活,最能符合需求的bash
所以 Aspects 的方案就是,對於待 hook 的 selector,將其指向 objc_msgForward,同時生成一個新的 aliasSelector 指向原來的 IMP,而且 hook 住 forwardInvocation 函數,使他指向本身的實現。按照上面的思路,當被 hook 的 selector 被執行的時候,首先根據 selector 找到了 objc_msgForward ,而這個會觸發消息轉發,從而進入 forwardInvocation。同時因爲 forwardInvocation 的指向也被修改了,所以會轉入新的 forwardInvocation 函數,在裏面執行須要嵌入的附加代碼,完成以後,再轉回原來的 IMP。框架
Aspects hook的過程ide
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
//首先判斷
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error))
{
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier)
{
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}//if (identifier)
}//if (aspect_isSelectorAllowedAndTrack(self, selector, options, error))
});
return identifier;
}
複製代碼
在沒有hook以前,ViewController的SEL與IMP關係以下 函數
最初的viewWillAppear: 指向了_objc_msgForward
增長了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP
最初的forwardInvocation:指向了Aspect提供的一個C方法__ASPECTS_ARE_BEING_CALLED__
動態增長了__aspects_forwardInvocation:,
指向最初的forwardInvocation:的IMP
複製代碼
而後,咱們再來看看hook後,一個viewWillAppear:的實際調用順序:ui
2.object收到selector(viewWillAppear:)的消息
2.找到對應的IMP:_objc_msgForward,執行後觸發消息轉發機制。
3.object收到forwardInvocation:消息
4.找到對應的IMP:__ASPECTS_ARE_BEING_CALLED__,執行IMP
複製代碼
//__ASPECTS_ARE_BEING_CALLED__中的邏輯
1.向object對象發送aspects_viewWillAppear:執行最初的viewWillAppear方法的IMP
2.執行插入的block代碼
3.若是ViewController沒法響應aspects_viewWillAppear,則向object對象發送__aspects_forwardInvocation:來執行最初的forwardInvocation IMP
複製代碼
一、判斷可否hook 對Class和MetaClass進行進行合法性檢查,判斷可否hook,規則以下 1).retain,release,autorelease,forwoardInvocation:不能被hook 2).dealloc只能在方法前hook 3).類的繼承關係中,同一個方法只能被hook一次
2.建立AspectsContainer對象, 以"aspects_ "+ SEL爲key,做爲關聯對象依附到被hook 的對象上
3.建立AspectIdentifier對象,而且添加到AspectsContainer對象裏存儲起來。這個過程分爲兩步 生成block的方法簽名NSMethodSignature 對比block的方法簽名和待hook的方法簽名是否兼容(參數個數,按照順序的類型) 4.根據hook實例對象/類對象/類元對象的方法作不一樣處理。
A)類方法來hook的時候,分爲兩步
1.hook類對象的forwoardInvocation:方法,指向一個靜態的C方法,
2.而且建立一個aspects_ forwoardInvocation:動態添加到以前的類中
3.hook類對象的viewWillAppear:方法讓其指向_objc_msgForward,
4.動態添加aspects_viewWillAppear:指向最初的viewWillAppear:實現
複製代碼
B)Hook實例的方法
Aspects支持只hook一個對象的實例方法
只不過在第4步略有出入,當hook一個對象的實例方法的時候:
1.新建一個子類,_Aspects_ViewController,而且按照上述的方式hook forwoardInvocation:
2.hook _Aspects_ViewController的class方法,讓其返回ViewController
hook _Aspects_ViewController_MetaClass,讓其返回ViewController
3.調用objc_setClass來修改ViewController的類爲_Aspects_ViewController
這樣作,就能夠經過object_getClass(self)得到類名,而後看看是否有前綴類名來判斷是否被hook過了
複製代碼
hook實例方法詳解
TestClass *testObj = [[TestClass alloc] init];
[testObj aspect_hookSelector:NSSelectorFromString(@"testSelector")
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"Hook testSelector");
}
error:NULL];
[testObj testSelector];
複製代碼
hook的過程: 一、經過statedClass = self.class獲取self原本的class (class方法被重寫了,用來獲取self被hook以前的Class(Target))
二、經過Class baseClass = object_getClass(self)獲取self的isa指針實際指向的class (self在運行時實際的class,表面上看這是一個西瓜(statedClass),實際上這是一個蘋果(basedClass))
三、若是baseClass(實際指向的class)已是被hook過的子類,則返回baseClass。
4.若是baseClass是MetaClass或者被KVO過的Class,則沒必要再生成subClass,直接在其自身上進行method swizzling。
5.若是不是上述三、4 所述狀況,默認狀況下須要對被hook的Class進行」isa swizzling」:
1)經過subclass = objc_allocateClassPair(baseClass, subclassName, 0)動態建立一個被hook類(TestClass)的子類(TestClass_Aspects); 2)而後對子類(TestClass_Aspects)的forwardInvocation:進行method swizzling,替換爲_ASPECTS_ARE_BEING_CALLED_,進行消息轉發時,實際執行的是_ASPECTS_ARE_BEING_CALLED_中的方法; 3)重寫子類(TestClass_Aspects)的獲取類名的方法class,使其返回被hook以前的類的類名(TestClass); 4)將self(TestObj)的isa指針指向子類(TestClass_Aspects)
object_setClass(self, subclass)
//object_setClass將一個對象設置爲別的類類型,返回原來的Class
複製代碼
class被hook後的狀況: