Aspects 是什麼,解決了什麼問題?
Aspects是AOP(面向切面程式設計)思想在iOS下OC的實作。Aspects可以用于hook函數,讓函數執行一些副操作(列印調試資訊、記錄日志等)。切面可以簡單了解為嵌入不同函數中的功能相同的操作(列印調試資訊等),每類功能相同的操作可以抽取出一個切面。下面簡要介紹OOP(面向對象程式設計)和AOP的概念和差別:
- OOP(面向對象程式設計)針對業務處理過程的實體及其屬性和行為進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
- AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。
OOP實際上是對對象的屬性和行為的封裝,而AOP對于這點就無從談起,但是AOP是處理某個步驟和階段的,從中進行切面的提取,也就是說,如果幾個或更多個邏輯過程中,有重複的操作行為,AOP就可以提取出來,運用動态代理,實作程式功能的統一維護,這麼說來可能太含蓄,如果說到權限判斷,日志記錄等,可能就明白了。如果我們單純使用OOP,那麼權限判斷怎麼辦?在每個操作前都加入權限判斷?日志記錄怎麼辦?在每個方法裡的開始、結束、異常的地方手動添加日志?所有,如果使用AOP就可以借助代理完成這些重複的操作,就能夠在邏輯過程中,降低各部分之間的耦合了。二者揚長補短,互相結合最好。
Aspects的思路及實作
思路
Aspects對外暴露了兩個接口,用于hook selector:
#pragma mark - Public Aspects API
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
/// @return A token which allows to later deregister the aspect.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
參數說明:
selector | options | block | error |
---|---|---|---|
要被hook的selector | 切面(block)執行的時機,在selector執行前、後執行,還是替換被hook的selector | 切面 | hook過程中的錯誤 |
方法說明:
- 第一個方法為類方法,receiver為要被hook的類(
)。直接在本類上hook類的執行個體方法(被KVO過的類,直接在KVO過程中生成的子類上進行hook執行個體方法),進行method swizzling,對類的methodLists[receiver message]
進行修改,修改的方法有兩個:struct objc_method_list **methodLists
-
forwardInvocation:
forwardInvocation: 的IMP(方法實作)被替換為:
,該函數内部具體執行被hook的selector和切入的操作,forwardInvocation: 的本來的IMP被儲存在__ASPECTS_ARE_BEING_CALLED__
中。forwardInvocation:的method swizzling操作是在__aspects_forwardInvocation:
中進行的;static Class aspect_hookClass(NSObject *self, NSError **error)
- 被hook的selector。被hook的selector的IMP被替換為:
/_objc_msgForward
,該方法用于觸發消息轉發,被hook的selector的本來的IMP被儲存在_objc_msgForward_stret
中。被hook的selector 的method swizzling 是在aliasSelector
中進行的。static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error)
-
- 第二個方法為執行個體方法,receiver為要被hook的類的執行個體。用該方法hook執行個體方法較複雜,步驟簡述如下:
- 新生成一個被hook類的子類,
- 利用isa swizzle将該執行個體的isa
指向新生成的被hook類的子類,Class isa
- 對被hook類的子類進行method swizzling,method swizzling的思路與上面類方法的method swizzling相同。
這裡直接拿世健同學的例子來講解吧:
@interface Target : NSObject
- (void)targetSelector;
@end
- (void)targetSelector
{
NSLog(@"%@'s original selector: %@", self, NSStringFromSelector(_cmd));
}
我們要對Target類的targetSelector進行hook,以方法二(執行個體方法)為例進行講解,假定aTarget為Target類的一個執行個體方法。
Target *aTarget = [[Target alloc] init];
[aTarget aspect_hookSelector:NSSelectorFromString(@"targetSelector")
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"Hook targetSelector");
}
error:NULL];
[aTarget targetSelector];
hook之前aTarget及targetSelector的指向:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICNxIjNzgTM1EDNwATM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
hook的過程:
hook Class(“isa swizzling”):
- 通過
擷取self本來的class(class方法被重寫了,用來擷取self被hook之前的Class(Target)。下文交代class方法是如何被重寫的);statedClass = self.class
- 通過
擷取self的isa指針實際指向的class(self在運作時實際的class,表面上看這是一隻皮鞋(statedClass),實際上這是一隻刮胡刀(basedClass))。Class baseClass = object_getClass(self)
- 如果baseClass(實際指向的class)已經是被hook過的子類,則傳回baseClass。
- 如果baseClass是MetaClass或者被KVO過的Class,則不必再生成subClass,直接在其自身上進行method swizzling。
- 如果不是上述3. 、4. 所述情況,預設情況下需要對被hook的Class進行”isa swizzling”:
- 通過
動态建立一個被hook類(subclass = objc_allocateClassPair(baseClass, subclassName, 0)
)的子類(Target
);Target_Aspects_
- 然後對子類的
進行method swizzling,替換為forwardInvocation:
,進行消息轉發時,實際執行的是__ASPECTS_ARE_BEING_CALLED__
中的方法;__ASPECTS_ARE_BEING_CALLED__
- 重寫子類的擷取類名的方法
,使其傳回被hook之前的類的類名;class
- 将self(aTarget)的isa指針指向子類
(Target_Aspects_
)。object_setClass(self, subclass)
- 通過
class被hook後的情況:
#pragma mark - Hook Class
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, );
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
hook selector:
- 用新生成的aspects__targetSelector指向selector的IMP;
- 将selector指向_objc_msgForward(或者_objc_msgForward_stret,與前者的差別為傳回值的不同,該實作傳回結構體?)。
selector 被hook後:
經過上面hook class和hook selector,Target的targetSelector就被hook進“切面”了。當調用
[aTarget targetSelector]
時,程式流程如下:
參考:
http://www.cnblogs.com/jyh317/p/3834271.html 簡單了解AOP(面向切面程式設計)
http://blog.ibireme.com/2013/11/26/objective-c-messaging/ Objective-C 中的消息與消息轉發
http://blog.cnbang.net/tech/2855/ JSPatch實作原理詳解<二>