AOP簡(jiǎn)介
AOP全名為 Aspect Oriented Programming- 面向切面編程。AOP是OOP(Object-Oriented Programing - 面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。 OOP引入封裝、繼承和多態(tài)等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合。當(dāng)我們需要為分散的對(duì)象引入公共行為的時(shí)候,OOP則顯得無(wú)能為力。也就是說(shuō),OOP允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對(duì)象層次中,而與它所散布到的對(duì)象的核心功能毫無(wú)關(guān)系。對(duì)于其他類(lèi)型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無(wú)關(guān)的代碼被稱(chēng)為橫切(cross-cutting)代碼,在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
AOP技術(shù)其實(shí)是對(duì)OOP設(shè)計(jì)的對(duì)象,利用一種稱(chēng)為“橫切”的技術(shù),剖解開(kāi)封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類(lèi)的公共行為封裝到一個(gè)可重用模塊,并將其名為“Aspect”,即方面。所謂“方面”,簡(jiǎn)單地說(shuō),就是將那些與業(yè)務(wù)無(wú)關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來(lái),便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來(lái)的可操作性和可維護(hù)性。
---- 想了解更詳細(xì)的AOP思想可以查看這邊文章 《團(tuán)隊(duì)開(kāi)發(fā)框架實(shí)戰(zhàn)—面向切面的編程 AOP》 ,以上總結(jié)也是摘自這篇文章。
舉個(gè)例子,我們需要統(tǒng)計(jì)用戶(hù)的行為,看下用戶(hù)對(duì)app的興趣分布熱點(diǎn),此時(shí)通常需要在多個(gè)控制器的 viewWillAppear:方法中加如處理統(tǒng)計(jì)的代碼,這些代碼都是與業(yè)務(wù)邏輯無(wú)關(guān)的,而且分散在多個(gè)模塊中,此時(shí)我們可利用AOP技術(shù),把這些重復(fù)、分散的代碼提取出來(lái)成為一個(gè)獨(dú)立的模塊。這樣既減少了系統(tǒng)的重復(fù)代碼,也降低了模塊的耦合度。好處還是十分明顯的。
Aspects初步認(rèn)識(shí)及使用
這是iOS開(kāi)發(fā)中實(shí)現(xiàn)AOP的一個(gè)輕量級(jí)框架 , 它就提供了兩個(gè)接口實(shí)現(xiàn)AOP ,這兩個(gè)方法都是 NSObject的分類(lèi)方法
//為某個(gè)所有類(lèi) 對(duì)象的selector 進(jìn)行切面 添加AOP實(shí)現(xiàn)
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
//為某個(gè)對(duì)象的selector 進(jìn)行切面 添加AOP實(shí)現(xiàn)
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
例如我要為所有繼承自UIViewController的對(duì)象的viewWillAppear:添加切面實(shí)現(xiàn)
[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo , BOOL animated ){
NSLog(@"成功進(jìn)行了切面");
}error:NULL];
在程序執(zhí)行完上面這句代碼后,你就成功的所有控制器的viewWillAppear :方法hook, 在方法執(zhí)行完原實(shí)現(xiàn)后,都會(huì)執(zhí)行上面的Block中的打印 ,方法中返回的協(xié)議<AspectToken>對(duì)象,可用于移除你添加的hook(調(diào)用 協(xié)議對(duì)象的 -remove方法)。而Aspect的實(shí)例方法- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; 是對(duì)單一對(duì)象的某個(gè)方法進(jìn)行hook , 效果跟例子中的類(lèi)方法差不多,這里就不在過(guò)多介紹。
Aspects內(nèi)部實(shí)現(xiàn)分析
Apects框架涉及的底層只是比較多,建議先了解清楚一下幾只知識(shí)點(diǎn)后再看源碼實(shí)現(xiàn)可能會(huì)比較容易理解。
- Block的內(nèi)部結(jié)構(gòu)及實(shí)現(xiàn)原理
- runtime的消息轉(zhuǎn)發(fā)機(jī)制
- OC中對(duì)象的 isa 指針 + KVO的實(shí)現(xiàn)原理
由于這個(gè)框架的設(shè)計(jì)中,對(duì)象間關(guān)系的關(guān)系比較復(fù)雜,這里我先簡(jiǎn)單介紹一下,Aspects中定義的類(lèi)的作用。
AspectInfo:
1. 作為私有對(duì)象 :用于在定義block時(shí)作為其第一個(gè)參數(shù) , 并且原方法發(fā)起調(diào)用時(shí) , 作為封裝調(diào)用參數(shù)(實(shí)參,用NSInvocation包裝)的對(duì)象
2. 作為公有協(xié)議(面向接口調(diào)用) : 為外界提供了一個(gè)協(xié)議,方便訪(fǎng)問(wèn)私有對(duì)象的屬性 。
AspectIdentifier:用于封裝定義hook時(shí)傳進(jìn)來(lái)的block,方法的調(diào)用者target , selector ,切面時(shí)機(jī)選項(xiàng)(AspectOptions - 位移枚舉) , 以及在調(diào)用完添加hook方法時(shí)作為返回值返回給調(diào)用者 ,遵守協(xié)議AspectToken用于移除hook, 執(zhí)行hook事件的執(zhí)行處理。一個(gè)hook事件對(duì)應(yīng)一個(gè)AspectIdentifier對(duì)象。
AspectTracker: 用于追蹤或記錄曾經(jīng)hock過(guò)的Class(調(diào)用類(lèi)方法進(jìn)行hook的類(lèi),不包括實(shí)例對(duì)象的hook) , 以防止對(duì)同一個(gè)集繼承體系的Class對(duì)同一個(gè)實(shí)例方法進(jìn)行重復(fù)hook
AspectsContainer: 用三數(shù)組(beforeAspects , insteadAspects , afterAspects)分別記錄對(duì)應(yīng)時(shí)機(jī)進(jìn)行hock的標(biāo)識(shí)對(duì)象AspectIdentifier,為hock提供數(shù)據(jù)存儲(chǔ)及支持 , 作為一個(gè)對(duì)象的屬性(通過(guò)runtime 關(guān)聯(lián)對(duì)象綁定在被hook的對(duì)象(實(shí)例對(duì)象 或 類(lèi)型對(duì)象))。
AspectToken: 用于移除hook的一個(gè)協(xié)議(只有一個(gè)方法 :-remove) ,AspectIdentifier就是遵循該協(xié)議的類(lèi)
先大概了解框架進(jìn)行hook時(shí)對(duì)類(lèi)的處理宏觀處理圖解,更有利于對(duì)細(xì)節(jié)處理的理解及分析。
下圖是對(duì)某個(gè)Class的Selector進(jìn)行了hook處理后的類(lèi)內(nèi)變化情況。

Asepcts的核心步驟:把要進(jìn)行hook的selector的IMP直接更換為runtime中的消息轉(zhuǎn)發(fā)的IMP (_objc_msgForward 或 _objc_msgForward_stret) , 讓外界調(diào)用改selector的時(shí)候直接進(jìn)入到消息轉(zhuǎn)發(fā) (注意:這里的原理與熱修復(fù)框架JSPatch的原理是一樣的,因此這兩個(gè)框架的共存是有問(wèn)題的),從而調(diào)用到方法-forwardInvocation:方法中,此時(shí)Class的-forwardInvocation :的實(shí)現(xiàn)已經(jīng)被框架替換為自定義函數(shù) __ASPECTS_ARE_BEING_CALLED__ , 從而成功進(jìn)行hock處理。
下面我們來(lái)分析Asepcts的具體實(shí)現(xiàn)
我們先看兩個(gè)添加hook處理的方法 , 其中兩個(gè)都是NSObjct的分類(lèi)方法:
//NSObject的類(lèi)方法
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
//NSObject的實(shí)例方法
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
其中這兩個(gè)方法都調(diào)用了下面函數(shù)
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * __autoreleasing *error)
上面函數(shù)的 形參 id self ,為什么可以同時(shí)接受實(shí)例方法的對(duì)象和類(lèi)方法中Class呢?其實(shí)理解這個(gè)問(wèn)題的實(shí)質(zhì)我覺(jué)得需要理解OC中對(duì)對(duì)象的定義。我們看來(lái)看下下面有關(guān)對(duì)象定義的源碼:
其實(shí)OC中Class也是對(duì)象 ,我們可以看看他們?nèi)齻€(gè)(id , Class,NSObject *)在runtime中的定義
// Class其實(shí)是 結(jié)構(gòu)體 objc_class * 的指針
typedef struct objc_class *Class;
// id其實(shí)是 結(jié)構(gòu)體 objc_object * 的指針 別名
typedef struct objc_object *id;
//OC的基類(lèi) NSObject 的聲明 - 可以理解為其實(shí)可以看做是首地址為指向 objc_class * (Isa)指針的內(nèi)存 都可以看做是對(duì)象
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
通過(guò)上面的源碼我盟可以總結(jié)出幾點(diǎn):
- Class其實(shí)是 結(jié)構(gòu)體 objc_class * 的指針 別名
- id其實(shí)是 結(jié)構(gòu)體 objc_object * 的指針 別名
- 結(jié)構(gòu)體
objc_object與OC中的NSObject的首地址都是指向isa,可以理解為首地址為ISA指針的內(nèi)存都可以稱(chēng)為對(duì)象。 -
objc_class是繼承自objc_object,也就是說(shuō),OC中Class也是一個(gè)對(duì)象。因此框架中無(wú)論是hook的實(shí)例方法還是hook的類(lèi)方法,都可以統(tǒng)一把 調(diào)用的對(duì)象(Class或NSObject *) 傳參 給了id類(lèi)型。
添加hook的實(shí)現(xiàn)
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * __autoreleasing *error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
//自璇鎖 , 保證block中的線(xiàn)程安全
aspect_performLocked(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//類(lèi)型懶加載 利用runtime 的屬性關(guān)聯(lián) 添加屬性 __aspects__selector -> AspectsContainer
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//生成hook的對(duì)應(yīng)標(biāo)識(shí) AspectIdentifier
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 添加 identifier 到 aspectContainer 的相應(yīng)數(shù)組
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
上面方法中的加鎖 aspect_performLocked 函數(shù),保證了block中的資源在多線(xiàn)程下讀取安全 ,實(shí)現(xiàn)如下
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
block();
OSSpinLockUnlock(&aspect_lock);
}
注意 :但是OSSpinLock這個(gè)鎖系統(tǒng)提示已經(jīng)過(guò)期了,而且這個(gè)鎖在多線(xiàn)程中如果線(xiàn)程的優(yōu)先級(jí)不同,會(huì)造成鎖無(wú)法釋放等問(wèn)題,詳細(xì)可以看下這篇文章不再安全的 OSSpinLock
添加hook工作之一 : 檢查能否 hook
aspect_isSelectorAllowedAndTrack 檢查hook的可行性 , 并利用AspectTracker 處理防止對(duì)一個(gè)類(lèi)的某個(gè)方法進(jìn)行重復(fù)hook 。這個(gè)方法會(huì)分別過(guò)濾掉不能hook的黑名單方法 (retain , release ,autorelease ,forwardInvocation: ,retain ), 如果是對(duì)整個(gè)類(lèi)的某個(gè)selector進(jìn)行 hook (發(fā)生在調(diào)用Aspects框架的類(lèi)方法進(jìn)行 hook), 還會(huì)進(jìn)行一個(gè)額外的處理 ,利用AspectTracker檢查 、記錄并追蹤Class的 hook 情況,在一個(gè)Class 第一次被hook時(shí),在其向上的繼承關(guān)系中都會(huì)在全局的容器中保存下hook的記錄具體的實(shí)現(xiàn) ,為了避免在一個(gè)繼承關(guān)系鏈中重復(fù)對(duì)同一個(gè)selector進(jìn)行hook ,具體可以看下這段代碼邏輯及注釋
//查看 self 的 isa 指針是否是metaClass , 這下面的處理都是針對(duì) 對(duì) 整個(gè)class的所有實(shí)例對(duì)象的實(shí)例方法進(jìn)行hock
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
//全局變量記錄所有被hook的Class(系統(tǒng)或自定義的類(lèi))
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
//獲取類(lèi)
Class currentClass = [self class];
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) { //證明曾經(jīng) 對(duì)selector hock 過(guò)
//判斷要hock的方法 , 在對(duì)應(yīng)的子類(lèi)是否有hock過(guò)同一個(gè)selector ,子類(lèi)hock過(guò)了 ,就不能再對(duì)父類(lèi)hock
// Find the topmost class for the log.
if (tracker.parentEntry) { //證明子類(lèi)已經(jīng)對(duì)selector hock過(guò)了 ,下面的邏輯主要是找出具體那個(gè)子類(lèi)被hook , 該類(lèi)的 parentEntry = nil
AspectTracker *topmostEntry = tracker.parentEntry;
while (topmostEntry.parentEntry) {
topmostEntry = topmostEntry.parentEntry;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}else if (klass == currentClass) { //這里表示以前對(duì)class的selector進(jìn)行過(guò)hook , 現(xiàn)在從新在該類(lèi)中對(duì)selector定義hook事件
// hook的已經(jīng)是最頂曾的類(lèi)了(oc中的子類(lèi) 例如:UIButton , UIImagView ),進(jìn)行行過(guò)hook,因此會(huì) 沒(méi)有 parentEntry , 這里并沒(méi)有執(zhí)行下面的while語(yǔ)句
// Already modified and topmost!
return YES;
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
// 執(zhí)行到這里證明 selector 可以 hook , 在整個(gè)向上的繼承體系中(父類(lèi))生成hook的記錄 對(duì)應(yīng)關(guān)系 : AspectTracker -> 進(jìn)行hook的Class + selectorName
currentClass = klass;
AspectTracker *parentTracker = nil; //實(shí)際添加hook的類(lèi) 沒(méi)有這個(gè)parentTracker
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
}
添加hook工作之二 : hook定義的參數(shù)存儲(chǔ) + Block 有效性驗(yàn)證
//類(lèi)似懶加載 利用runtime 的屬性關(guān)聯(lián) 添加屬性 __aspects__selector -> AspectsContainer
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//生成hook的對(duì)應(yīng)標(biāo)識(shí) AspectIdentifier
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 添加 identifier 到 aspectContainer 的相應(yīng)數(shù)組
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
如果允許hook , 一個(gè) hook的定義對(duì)應(yīng)一個(gè) AspectIdentifier。一個(gè)對(duì)象(類(lèi)也是對(duì)象)的所有hook都存放在這個(gè)對(duì)象通過(guò)runtime的對(duì)象關(guān)聯(lián)綁定的屬性中 ,該屬性類(lèi)型為AspectsContainer,根據(jù)hook定義時(shí)傳進(jìn)來(lái)的options參數(shù)分別加入到 AspectContainer 對(duì)應(yīng)的數(shù)組
-
beforeAspects- selecter執(zhí)行前進(jìn)行的hook處理 -
insteadAspects- 替換調(diào)selecter執(zhí)行hook處理 -
afterAspects- selecter執(zhí)行后進(jìn)行的hook處理
注意:我們看下AspectContainer中的屬性聲明 , 三個(gè)數(shù)組都是聲明為 atomic ,來(lái)保證多線(xiàn)層的讀取安全。這也是框架作者在開(kāi)始時(shí)就提示我們,建議不要對(duì)調(diào)用頻繁的方法進(jìn)行hook的原因之一。
// AspectContainer數(shù)組屬性聲明
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
AspectIdentifier 這個(gè)類(lèi)在初始化時(shí)還做了selector 和 執(zhí)行hook的Block的參數(shù)校驗(yàn)。
AspectIdentifier的初始化方法:主要是驗(yàn)證完block與hook sel ector的參數(shù)類(lèi)型是否符合要求后,才完成初始化的操作 , 如果不符合要求直接返nil結(jié)束初始化,代碼實(shí)現(xiàn)如下:
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
NSCParameterAssert(block);
NSCParameterAssert(selector);
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
return nil;
}
//selector 與 block 參數(shù)匹配后 生成 AspectIdentifier (參數(shù)的個(gè)數(shù)、類(lèi)型一樣)
AspectIdentifier *identifier = nil;
if (blockSignature) {
identifier = [AspectIdentifier new];
identifier.selector = selector;
identifier.block = block;
identifier.blockSignature = blockSignature;
identifier.options = options;
identifier.object = object; // weak
}
return identifier;
}
AspectIdentifier初始化方法中調(diào)用的獲取Block簽名字符的函數(shù):這個(gè)函數(shù)主要是根據(jù)Block( Block在編譯成C語(yǔ)言后其實(shí)是一個(gè)結(jié)構(gòu)體)的內(nèi)部結(jié)構(gòu),操作指針的位移數(shù)來(lái)獲取到簽名參數(shù)字符串,并色很生成 NSMethodSignature返回。 更詳細(xì)的原理 ,可以看下我之前寫(xiě)的《淺析Block的內(nèi)部結(jié)構(gòu) 及其 如何利用 NSInvocation 進(jìn)行調(diào)用》
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];
}
Block與hook selector的參數(shù)校驗(yàn)函數(shù):主要是獲取selector的參數(shù)數(shù)量和方法簽名字符串跟Block的簽名字符串比較它們是否一致,這里解析一下for循環(huán)為什么是從2開(kāi)始遍歷的
block執(zhí)行調(diào)用時(shí)所傳的參數(shù):
0 . block本身(encodeType = @?)
1 . 其他自定義的參數(shù)(這里的Block 索引為1 的位置為 id<ApsectInfo> aspectInfo)selector 執(zhí)行調(diào)用時(shí)所傳的參數(shù):
0.id object 方法調(diào)用者
1.selector 方法本省
2 .其他自定義的參數(shù)
所以這里要校對(duì)的是自定義參數(shù)的是否一致,這里Block的前兩個(gè)參數(shù)分別是Block本身,以及一個(gè)id<ApsectInfo>類(lèi)型的對(duì)象。所以從第二個(gè)索引開(kāi)始比較自定義參數(shù)的類(lèi)型。
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
if (blockType[0] != '@') {
signaturesMatch = NO;
}
}
// Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
// The block can have less arguments than the method, that's ok.
if (signaturesMatch) {
for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
// Only compare parameter, not the optional type data.
if (!methodType || !blockType || methodType[0] != blockType[0]) {
signaturesMatch = NO; break;
}
}
}
}
if (!signaturesMatch) {
NSString *description = [NSString stringWithFormat:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];
AspectError(AspectErrorIncompatibleBlockSignature, description);
return NO;
}
return YES;
}
添加hook工作之三 : 方法的交換處理
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
//獲取klass 獲取進(jìn)行hook處理的Class,主要是替換 forwardInvocation:方法的 IMP 。
Class klass = aspect_hookClass(self, error);
//獲取原來(lái)方法的IMP
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
//Make a method alias for the existing method implementation, it not already copied.
//selector的IMP替換為 消息轉(zhuǎn)發(fā)的IMP
//aspects__selector的IMP替換為 最初selector的IMP
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) { //判斷klass 是否能響應(yīng)aliasSelector ,不能的話(huà)就添加aliasSelector方法 , 實(shí)現(xiàn)為原來(lái)selector的實(shí)現(xiàn)IMP
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in. 讓原來(lái)的selector 直接進(jìn)入消息轉(zhuǎn)發(fā) forwardInvocaction:
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
首先獲取到要hook的Class , 然后判斷要hook的selector 的 IMP是不是進(jìn)入 消息轉(zhuǎn)發(fā)的 IMP 是的話(huà)就默認(rèn)已經(jīng)完成了框架中進(jìn)行hook的準(zhǔn)備工作了。如果不是的話(huà)繼續(xù)進(jìn)行 if代碼塊里的處理邏輯
- 添加方法,名字為 aspects__selector(selector為要hook的方法名) , 使其
IMP指向selector的IMP。 - 把
selector的IMP指向消息轉(zhuǎn)發(fā)的IMP, 這是外界調(diào)用這個(gè)selector直接進(jìn)入消息轉(zhuǎn)發(fā),從而調(diào)用到被處理過(guò)的forwardInvocation:
經(jīng)過(guò)處理后 ,外界調(diào)用selector是就可以進(jìn)入了消息轉(zhuǎn)發(fā)的forwardInvocation:方法
接下來(lái)看下獲取hook Class時(shí)的代碼實(shí)現(xiàn)
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]) { //className 如果有 _Aspects_ 前綴 , 以前hock做的實(shí)例對(duì)象
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) { //self 是 Class
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
//測(cè)試 :對(duì)UILabel對(duì)象進(jìn)行kvo后 class == UILabel , get_class == NSKVONotifying_UILabel
//Aspect在gitHub上的issues上有人已經(jīng)解決了KVO沖突的方案 https://github.com/steipete/Aspects/pull/115
}else if (statedClass != baseClass) { //self 是 被KVO的對(duì)象 , 需要把 NSKVONotifying_ClassName 的 forwardInvocation: 替換處理
return aspect_swizzleClassInPlace(baseClass);
}
//self是普通object , 創(chuàng)建一個(gè) aspect__前綴的子類(lèi) , 并把 self的 isa 指向新的子類(lèi)
//不用吧新建的class加入全局class中記錄
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
//從來(lái)沒(méi)有創(chuàng)建過(guò)這個(gè)類(lèi)的話(huà),就從新創(chuàng)建。創(chuàng)建過(guò)的話(huà),在runtime中會(huì)有記錄,Class類(lèi)似單例
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
//讓新創(chuàng)建的類(lèi) forwardInvocation -> __ASPECTS_ARE_BEING_CALLED__ , __aspects_forwardInvocation -> originalImplementation(forwardInvocation)
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
//修改isa指針
object_setClass(self, subclass);
return subclass;
}
這個(gè)函數(shù)其實(shí)也是Aspect比較核心的部分,我們來(lái)詳細(xì)分析一下方法接下來(lái)做的事情
分支else if (class_isMetaClass(baseClass) 證明self 是一個(gè)類(lèi)(Class, 外界調(diào)用的是類(lèi)方法,對(duì)整一個(gè)類(lèi)進(jìn)行hook),調(diào)用return aspect_swizzleClassInPlace((Class)self); ,隨后調(diào)用到下面兩個(gè)函數(shù)
static Class aspect_swizzleClassInPlace(Class klass) {
NSCParameterAssert(klass);
NSString *className = NSStringFromClass(klass);
_aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
if (![swizzledClasses containsObject:className]) {
aspect_swizzleForwardInvocation(klass);
[swizzledClasses addObject:className];
}
});
return klass;
}
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
// If there is no method, replace will act like class_addMethod.
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
上面兩個(gè)函數(shù)是把傳進(jìn)來(lái)的Class做相應(yīng)處理處理
修改 Class 的方法列表 forwardInvocation: 方法的IMP 指向自定義函數(shù)___ASPECTS_ARE_BEING_CALLED____
添加 __aspects_forwardInvocation 方法 , 其IMP 原來(lái)的 forwardInvocation指向的IMP
修改完后把 ClassName 存放到全局的集合中 記錄 證明這個(gè)Class已經(jīng)是修改過(guò)了 消息轉(zhuǎn)發(fā)IMP了 , 避免以后重復(fù)對(duì)一個(gè)
Class進(jìn)行hook時(shí)重復(fù)做上面 1 ,2的步驟
如果執(zhí)行到分支else if (statedClass != baseClass) , 證明self是一個(gè)實(shí)例對(duì)象 , 并且這個(gè)實(shí)例對(duì)象是先被添加了KVO處理 ,再調(diào)用Aspects框架添加hook處理的對(duì)象 。 注意 :Apsects現(xiàn)在是不支持實(shí)例對(duì)象先被KVO,再添加hook處理的。程序會(huì)提示unrecognized selector sent to instance 然后崩掉 , 框架作者在Demo的測(cè)試代碼中也要相關(guān)說(shuō)明 , 也有人在github上給作者提了issue 詳細(xì)可以點(diǎn)擊這里查看
如果上面那幾個(gè)if else都沒(méi)有返回到hook class的話(huà),證明要hook的對(duì)象是一個(gè)普通實(shí)例對(duì)象 ,不是一個(gè) Class 。接下來(lái)將要類(lèi)似實(shí)現(xiàn)KVO的處理。
嘗試獲取一個(gè)類(lèi) (名為 :_ Aspects _實(shí)例對(duì)象的類(lèi)名) ,如果系統(tǒng)沒(méi)有的話(huà)就生成這個(gè)類(lèi),并且讓這個(gè)類(lèi)繼承自實(shí)例對(duì)象的類(lèi)
調(diào)用函數(shù)
aspect_swizzleForwardInvocation,做消息轉(zhuǎn)發(fā)方法IMP自定義處理(同上面3個(gè)步驟一樣)調(diào)用
aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass);修改對(duì)應(yīng)實(shí)例對(duì)象 和 類(lèi) 的 class 方法 返回對(duì)象hook之前Class。保持其行為與hook之前保持一致,從而不影響到外界的使用。
4.調(diào)用objc_registerClassPair(subclass); object_setClass(self, subclass); 把新生成的Class注冊(cè)到系統(tǒng)中 ,并且把實(shí)例對(duì)象的isa指向新的Class
通過(guò)生成一個(gè)新的Class,并修改實(shí)例對(duì)象的isa指向新 Class , 這樣處理的目的是,既為單個(gè)實(shí)例對(duì)象實(shí)現(xiàn)了hook處理 , 也不會(huì)影響到其他同類(lèi)的實(shí)例對(duì)象 。其實(shí)KVO也是通過(guò)同樣得原理實(shí)現(xiàn)的。