iOS 面向切面(AOP)編程 —— Aspects & BlockHook

前言:

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叮贩。而且需要調用NSInvocationretainArguments方法,主動讓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ū)探討

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末态坦,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子棒拂,更是在濱河造成了極大的恐慌伞梯,老刑警劉巖玫氢,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異谜诫,居然都是意外死亡漾峡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門喻旷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來生逸,“玉大人,你說我怎么就攤上這事且预〔郯溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵辣之,是天一觀的道長掰伸。 經常有香客問我,道長怀估,這世上最難降的妖魔是什么狮鸭? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮多搀,結果婚禮上歧蕉,老公的妹妹穿的比我還像新娘。我一直安慰自己康铭,他們只是感情好惯退,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著从藤,像睡著了一般催跪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夷野,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天懊蒸,我揣著相機與錄音,去河邊找鬼悯搔。 笑死骑丸,一個胖子當著我的面吹牛,可吹牛的內容都是我干的妒貌。 我是一名探鬼主播通危,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼灌曙!你這毒婦竟也來了菊碟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤在刺,失蹤者是張志新(化名)和其女友劉穎框沟,沒想到半個月后藏古,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡忍燥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年拧晕,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梅垄。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡厂捞,死狀恐怖,靈堂內的尸體忽然破棺而出队丝,到底是詐尸還是另有隱情靡馁,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布机久,位于F島的核電站臭墨,受9級特大地震影響,放射性物質發(fā)生泄漏膘盖。R本人自食惡果不足惜胧弛,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侠畔。 院中可真熱鬧结缚,春花似錦、人聲如沸软棺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喘落。三九已至茵宪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘦棋,已是汗流浹背稀火。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兽狭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓鹿蜀,卻偏偏與公主長得像箕慧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茴恰,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345