Aspects 是 Objective-C 比較知名的 AOP 框架,實現方法調劑(method swizzling)。經過使用 Aspects 提供的接口,比直接使用 runtime 提供的接口,更加方便靈活。ios
Aspects 如今不建議在生產環境使用,但它的實現原理,仍是很是值得學習和借鑑的。git
Aspects 支持實例和類的方法的調劑,雖然內部調用的是同一個方法,在實現上有較大的差異。github
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
複製代碼
- aspect_hookSelector
內部調用的aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)
由 C 函數實現。該函數有五個參數:數組
self
:當前調劑方法的對象;selector
:被調劑的方法名;options
:調劑的方式類型爲 AspectOptions;block
:調劑selector
方法是須要混入執行的 block;error
: 調劑過程發生的錯誤信息;AspectOptions 有四種類型,用於決定 block 和原方法執行的順序,分別有在原方法以前、以後、或者直接替換。安全
咱們逐一看看 aspect_add 函數的內部實現:框架
__block AspectIdentifier *identifier = nil;
// 1
aspect_performLocked(^{
// 2
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// 3
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 4
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 5
[aspectContainer addAspect:identifier withOptions:options];
// 6
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
複製代碼
一、 爲方法調劑加鎖,保證線程安全,使用的是自旋鎖,然而 OSSpinLockLock 已再也不安全。ide
二、 判斷當前 selector 是否支持調劑。若是被調劑方法是 retain, release, autorelease, forwardInvocation:
則返回錯誤。forwardInvocation
方法不支持調劑是由於 Aspects 的實現基於這個方法,後面會講到。至於其餘幾個內存管理的方法,筆者不是很肯定不能調劑的緣由,多是在這些方法中對象處於不穩定狀態, 訪問當前對象存在安全隱患。函數
接着是保證調劑dealloc
方法只能是AspectPositionBefore
類型,dealloc 負責釋放資源,不能被替換,執行後對象釋放不能再執行 block 操做。學習
最後判斷當前對象可否響應被調劑的方法。ui
三、 aspectContainer
以關聯對象的形式做爲self
的屬性,屬性的名稱爲 selector
加前綴aspects_
。
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
複製代碼
AspectsContainer
類包含三個數組屬性,分別用於保存在 block 三種執行的狀況。
四、 AspectIdentifier
以對象的形式封裝調劑信息,其中包含方法名,執行的對象,執行時機,以及執行的 block。
五、 將 aspect identifier 存儲到當前對象關聯的 container 中。在方法被調用的時候,會遍歷這個 container 中全部的 identifier,根據它包含的信息來執行。
六、 這是最關鍵的一步,也是 Aspects 實現的核心所在。主要分爲兩部分,
1)處理被 hook 方法的對象所屬的類
Class aspect_hookClass(NSObject *self, NSError **error)
複製代碼
若是對象所屬的類已經被 Aspects 處理過就直接返回,不然該函數爲當前實例對象所屬的類,動態的建立一個子類,並 hook 該子類的forwardInvocation
方法,指向本身的__ASPECTS_ARE_BEING_CALLED__
函數實現。
重置該子類對象和元類的 class 方法,使其返回(父類)被 hook 方法所在的類名。最後將當前實例self
的isa
指針指向這個子類,做用是當self
接受消息時,會先在這個子類方法列表查找。
此時若是self
收到的消息消息沒法處理,會走forwardInvocation
對應的實現,而前面這個方法已經被 hook 指向咱們本身的實現,在咱們本身實現中,經過NSInvocation
能取出當前消息的接受對象,經過該對象從關聯屬性中取出 container 來執行。
如今的目標是若是經過調用被 hook 的方法,來觸發forwardInvocation
的調用。
2)處理被 hook 的實現 在 <objc/message.h> 頭文件中有一個函數_objc_msgForward
它的函數指針就是與 forwardInvocation
關聯的imp
。
只要咱們將 hook 的方法與 _objc_msgForward
實現交換,就能觸發forwardInvocation
的imp
了。
在調劑原selector
的imp
指向_objc_msgForward
函數以前,須要先判斷該 selector 是否已經被調劑過,若是已是_objc_msgForward
的實現就不做處理。
若是不是,咱們要對selector
的實現作交換了,假如咱們直接將其與 _objc_msgForward
交換,若是對象的另外一個selector
也作 hook,就會把前一個selector
的實現給覆蓋了。因此這裏動態的添加一個 aliasSelector
方法用於保存原selector
的實現,再用_objc_msgForward
函數替換原 selector 的實現。
當被調劑的方法執行時,會執行動態建立的子類的forwardInvocation
方法,而在1)
中咱們講到該方法的實現已經被 hook,指向了 __ASPECTS_ARE_BEING_CALLED__
函數。
__ASPECTS_ARE_BEING_CALLED__
函數的實現其實並不複雜,它從 forwardInvocation
的參數NSInvacation
中取出觸發消息轉發的 selector,將它替換爲aspects_
前綴的別名selector
,由於這個別名 selector
指向前面保存的原selector
的實現。
接着從self
中取出使用以別名selector
做爲屬性的關聯對象 AspectsContainer
,container 中包含selector
方法被調劑的信息集合。先執行 before 的 block,接着執行封裝在invocation
中的原實現,最後執行 after 類型的 block。若是有 instead 類型的 block 則原實現不會被執行。
其中 block 並非直接執行 aspect 中保存的那個,而是對其參數進行了處理,在第一個參數中插入 AspectInfo 類型的實例。
類的方法交換流程與實例的方法交換大體相同,主要區別在於對同一繼承鏈上的類 hook 相同的方法進行了限制。
實例和類調用的都是aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)
函數,因此 self
參數多是實例,也多是類,源碼中使用 object_getClass
,若是是實例對象會返回類,若是是類會返回元類,再經過class_isMetaClass
判斷self
是不是爲類。
在aspect_isSelectorAllowedAndTrack
函數中,對類的狀況作了一大堆處理,其實就作一件事情:判斷當前self
所在的繼承鏈是否有同名的 selector
已經被 hook 過了。
若是沒有,就記錄當前self
的繼承關係,直到基類NSObject
。
之因此不能 hook 具備繼承鏈關係同名方法,是由於若是子類方法調用super
會致使死循環。
關鍵緣由在於super
關鍵字的實現,它是編譯器的一個助記符,在方法調用時,會在父類的方法列表中查找,但無論在哪一層級找到,消息的接受者仍然是當前類/實例。也就是說在執行super
方法時,若是父類的該方法也被調用,就會走__ASPECTS_ARE_BEING_CALLED__
函數,該函數中的 NSInvocation
參數中的 target
依然是self
,這就致使[invocation invoke]
調用時,觸發的仍是當前self
的selector
。也就致使了循環調用。
- (void)helloInstanceMethod {
[super helloInstanceMethod]; // 致使 [self helloInstanceMethod]
}
複製代碼
Aspects 在類層面上進行方法的調劑時,直接「原地」調劑forwardInvocation
指向本身的實現,並將要 hook 的方法指向消息轉發的實現。同時它必須保證同一條繼承鏈上,不能 hook 同一個方法。
然而,對於實例對象的方法卻不須要,由於實例方法的 hook 會動態的建立子類,並修改消息轉發的實現,保證原類的其餘實例方法不會受影響。