前言:
Aspect Oriented Programming (AOP,面向切面編程) 在 Objective-C 社區(qū)內沒有那么有名樟结,但是 AOP 在運行時可以有巨大威力鸠删。 但是因為沒有事實上的標準悄窃,Apple 也沒有開箱即用的提供妻味,也顯得不重要白翻,開發(fā)者都不怎么考慮它喳瓣。
—— 引用自禪與 Objective-C 編程藝術
但在實際項目中有時需要集成統(tǒng)計SDK馋贤,比如 Google Analytics, Flurry, MatomoTracker, 等等。一般情況下是直接將統(tǒng)計代碼寫到對應的地方畏陕,比如需要統(tǒng)計某個界面的展示次數會將代碼寫在viewDidAppear:
方法內配乓,這就造成了很大的入侵性,并且view controller里的代碼將變糟糕起來惠毁。這時候就需要通過使用AOP將統(tǒng)計代碼單獨分離出來犹芹,這樣view controller不會被其它代碼污染,并且單獨分離出來以后擴展或者更換其它統(tǒng)計SDK會方便很多鞠绰。
在對類的特點方法進行切面可以使用Aspects腰埂,但是在一些特殊情況下統(tǒng)計代碼需要寫在block的回調內,這時就需要用上BlockHook蜈膨,比如需要在某個網絡請求成功的block回調內屿笼,這時候就需要Aspects & BlockHook配合使用。本文針對Aspects & BlockHook將分成兩個部分來講翁巍,主要講如何使用和使用中遇到的坑驴一。
Aspects:
Aspects一個基于runtime的輕量級AOP開源框架,作者Peter Steinberger
灶壶,主要是對方法進行Hook肝断,該框架簡單易用,源碼不到千行卻非常健全驰凛,考慮到了很多關于Hook方面的安全問題孝情。
基本用法:
Aspects暴露了兩個方法(方法名一樣),分別對應類方法和實例方法洒嗤,下面為使用示例:
SEL selektor = NSSelectorFromString(@"loginWithAccount:password:block:");
Class clazz = objc_getMetaClass(@"HYLoginNetwork".UTF8String);//類方法
//Class clazz = NSClassFromString(@"HYLoginNetwork");//實例方法
[clazz aspect_hookSelector:selektor
withOptions:AspectPositionAfter//在Hook方法 執(zhí)行完成之后 執(zhí)行usingBlock里的代碼
usingBlock:^(id<AspectInfo> aspectInfo, NSString *account, NSString *password, id block) {
//需要執(zhí)行的代碼...
}
error:nil];
通過字符串的方式創(chuàng)建selektor
方法名和clazz
對象箫荡,這樣可以減少過多的引入頭文件,輸入錯誤的方法名或對象名時會輸出錯誤日志渔隶。方法返回的AspectToken
對象可以通過remove
方法取消Hook羔挡。AspectOptions
代表何時執(zhí)行usingBlock
的代碼洁奈。usingBlock
的參數是動態(tài)參數,除了第一個參數aspectInfo
是固定的外绞灼,其它參數是Hook的方法對應的參數(按順序排列)利术。
AspectPositions:
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// 在原始實現后調用(默認)
AspectPositionInstead = 1, /// 將替換原始實現。
AspectPositionBefore = 2, /// 在原始實現之前調用低矮。
AspectOptionAutomaticRemoval = 1 << 3 /// 執(zhí)行一次后移除Hook
};
AspectInfo:
/// AspectInfo協(xié)議是usingBlock的第一個參數印叁。
@protocol AspectInfo <NSObject>
- (id)instance; /// 當前Hook的實例。
- (NSInvocation *)originalInvocation;/// 被 Hook 方法的原始 invocation
- (NSArray *)arguments;/// 被 Hook 方法的所有參數裝箱军掂。 這是懶惰的(懶加載的)轮蜕。
@end
方法有返回值?獲取返回值:
id returnValue;
[aspectInfo.originalInvocation getReturnValue:&returnValue];
BlockHook:
BlockHook是由楊蕭玉編寫并開源的框架蝗锥,基于 libffi
實現了對 Objective-C Block 的 hook跃洛。
基本用法:
[clazz aspect_hookSelector:selektor
withOptions:AspectPositionBefore //當block是__NSStackBlock__類型的情況下要在這個方法執(zhí)行前(AspectPositionBefore)copy到堆上
usingBlock:^(id<AspectInfo> aspectInfo) {
__unsafe_unretained id block = [self getLastArgument:aspectInfo];
[block block_hookWithMode:BlockHookModeAfter//在block執(zhí)行完之后調用
usingBlock:^(BHToken *token, NSInteger code){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
//需要執(zhí)行的代碼...
});
}];
}
error:nil];
BlockHookMode:
typedef NS_ENUM(NSUInteger, BlockHookMode) {
BlockHookModeAfter, /// 在原始實現后調用
BlockHookModeInstead, /// 將替換原始實現
BlockHookModeBefore, /// 在原始實現之前調用
BlockHookModeDead, /// 在block銷毀之后調用
};
BlockHook的API是參照Aspects寫的,所以懂得Aspects的一看就懂终议。和Aspects一樣汇竭,方法返回的BHToken
對象可以通過remove
方法取消Hook。BlockHookMode
代表何時執(zhí)行usingBlock
的代碼穴张。usingBlock
的參數是動態(tài)參數细燎,除了第一個參數BHToken
是固定的外,其它參數是Hook的Block對應的參數(按順序排列)皂甘。
但是需要注意的是找颓,當block是__NSStackBlock__
類型的情況下要在這個方法執(zhí)行前(AspectPositionBefore
)讓系統(tǒng)把Block copy
,否則Hook不到這個Block叮贩。而且需要調用NSInvocation
的retainArguments
方法,主動讓NSInvocation
把Block copy
到堆上佛析,否則從NSInvocation
獲取的__NSStackBlock__
類型block不會銷毀益老。
-(id)getLastArgument:(id<AspectInfo>)aspectInfo{
[aspectInfo.originalInvocation retainArguments];
__unsafe_unretained id block;
//取最后一個參數(網絡請求成功的blcok)
NSInteger index = aspectInfo.originalInvocation.methodSignature.numberOfArguments - 1;
[aspectInfo.originalInvocation getArgument:&block atIndex:index];
return block;
}
參考資料:
面向切面編程之 Aspects 源碼解析及應用
從 Aspects 源碼中我學到了什么?
iOS 如何實現Aspect Oriented Programming (上)
Hook Objective-C Block with Libffi
寫在最后:
原文:https://www.hlzhy.com/?p=109
當初為了讓BlockHook配合Aspects可沒少折騰啊寸莫,集成libffi.a問題(現在作者直接把libffi.a和相關頭文件集成在項目里了)捺萌,__NSStackBlock__
的問題也讓我困惑了好久。現在寫出這篇文章了似乎也并不是現象中的那么復雜??膘茎。
最后桃纯,如果此文章對你有幫助,希望給個??披坏。有什么問題歡迎在評論區(qū)探討