iOS 修改私有方法總結(jié)

iOS 修改私有方法總結(jié)

1.Category添加方法

原理:
通過(guò)Category添加方法婉宰,優(yōu)先于類中相同方法的特點(diǎn)貌笨,進(jìn)行私有函數(shù)修改耳奕。
category中的方法位于類方法列表的前面衅斩,在進(jìn)行方法查找時(shí)盆顾,會(huì)先找到category中的方法,所以會(huì)優(yōu)先執(zhí)行Category中方法的目的矛渴,達(dá)到攔截原有私有方法的目的椎扬。

副作用:

  • 不能直接調(diào)用 super,除了特殊的 + load 方法外具温,其他分類中的方法蚕涤,在被調(diào)用之前,并不會(huì)去調(diào)用原始方法铣猩,這將導(dǎo)致外部在使用這個(gè)類的時(shí)候揖铜,永遠(yuǎn)調(diào)用不到原始的實(shí)現(xiàn),尤其是一些系統(tǒng)類內(nèi)部互相調(diào)用的情況达皿,將會(huì)無(wú)法預(yù)估天吓;
  • 當(dāng)項(xiàng)目中存在多個(gè)分類復(fù)寫了同一個(gè)私有方法后,最終調(diào)用哪個(gè)分類中的方法將由編譯順序所決定峦椰,這又充滿了不確定性龄寞,因?yàn)榉诸愖陨聿⒉恢肋@個(gè)私有方法是否已經(jīng)被其他分類所復(fù)寫;
  • 一旦通過(guò)分類復(fù)寫私有方法汤功,它的影響將會(huì)是全局的物邑,所有用到這個(gè)類的地方,都將變成分類調(diào)用

2.Method Swizzling

原理:
通過(guò)使用runtime方法,交換函數(shù)imp地址色解,在調(diào)用原函數(shù)是茂嗓,就變成調(diào)用新函數(shù),達(dá)到修改的目的科阎。

示例代碼:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

副作用:

  • 只能執(zhí)行一次述吸,多次執(zhí)行會(huì)有問(wèn)題。
  • 要避免hook掉函數(shù)的父函數(shù)锣笨。
  • hook方法中含有_cmd這種函數(shù)蝌矛,會(huì)出現(xiàn)未知問(wèn)題。
  • 不支持實(shí)例對(duì)象的hook

使用場(chǎng)景:

3.objc_msgForward消息轉(zhuǎn)發(fā)

原理:
在 OC 中票唆,向一個(gè)對(duì)象發(fā)送消息后朴读,最終會(huì)全部轉(zhuǎn)化到 objc_msgSend 中屹徘,繼而在方法列表中通過(guò) SEL 開始查找對(duì)應(yīng)的 IMP走趋,如果沒有查到對(duì)應(yīng)的方法,則 IMP 會(huì)返回 objc_msgForward / objc_msgForward_stret噪伊,從而觸發(fā)未知消息轉(zhuǎn)發(fā)流程簿煌,即 resolveInstanceMethod:forwardingTargetForSelector:forwardInvocation:鉴吹。

Aspects主要是利用了forwardInvocation進(jìn)行轉(zhuǎn)發(fā)姨伟,Aspects其實(shí)利用和kvo類似的原理,通過(guò)動(dòng)態(tài)創(chuàng)建子類的方式豆励,把對(duì)應(yīng)的對(duì)象isa指針指向創(chuàng)建的子類夺荒,然后把子類的forwardInvocation的IMP替成__ASPECTS_ARE_BEING_CALLED__,假設(shè)要hook的方法名XX,在子類中添加一個(gè)Aspects_XX的方法良蒸,然后將Aspects_XXIMP指向原來(lái)的XX方法的IMP技扼,這樣方便后面調(diào)用原始的方法,再把要hook的方法XXIMP指向_objc_msgForward嫩痰,這樣就進(jìn)入了消息轉(zhuǎn)發(fā)流程剿吻,而forwardInvocationIMP被替換成了__ASPECTS_ARE_BEING_CALLED__,這樣就會(huì)進(jìn)入__ASPECTS_ARE_BEING_CALLED__進(jìn)行攔截處理串纺,這樣整個(gè)流程大概結(jié)束丽旅。

示例代碼:

    [UIViewController aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) {
        NSLog(@"Controller is about to be deallocated: %@", [info instance]);
    } error:NULL];

副作用:

  • Aspect 中使用了 OSSpinLockLock 來(lái)保證線程安全,但自旋鎖已經(jīng)被發(fā)現(xiàn)為存在優(yōu)先級(jí)調(diào)度的問(wèn)題
  • Aspect 中利用了 sub class 來(lái)實(shí)現(xiàn)消息轉(zhuǎn)發(fā)纺棺,但這種繼承鏈消息轉(zhuǎn)發(fā)存在缺陷榄笙,子類和父類如果同時(shí) hook 一個(gè)方法,便會(huì)造成死循環(huán)MessageThrottle Safety

4.基于libbfi動(dòng)態(tài)調(diào)用C函數(shù)

原理:
使用libffi中的ffi_closure_alloc構(gòu)造與原方法參數(shù)一致的"函數(shù)" -- stingerIMP祷蝌,以替換原方法函數(shù)指針茅撞;此外,生成了原方法和Block的調(diào)用的參數(shù)模板cifblockCif。方法調(diào)用時(shí)乡翅,最終會(huì)調(diào)用到void _st_ffi_function(ffi_cif *cif, void *ret, void **args, void *userdata), 在該函數(shù)內(nèi)鳞疲,可獲取到方法調(diào)用的所有參數(shù)、返回值位置蠕蚜,主要通過(guò)ffi_call根據(jù)cif調(diào)用原方法實(shí)現(xiàn)和切面block尚洽。AOP庫(kù) Stinger和BlockHook就是使用libbfi做的。

示例代碼:

BlockHook使用
    void(^block)(void) = ^() {
        NSLog(@"This is global block!");
    };
    [block block_hookWithMode:BlockHookModeAfter usingBlock:^(BHToken *token) {
        NSLog(@"After global block!");
    }];

使用場(chǎng)景:
目前使用場(chǎng)景較多靶累,執(zhí)行效率較高腺毫,StingerBlockHook都是基于libbfi進(jìn)行開發(fā)的。

5.基于橋的全量方法 Hook 方案 - TrampolineHook

原理:
把一個(gè)我們要替換的原方法 IMP A 取出來(lái)挣柬,保存起來(lái)潮酒。
給這個(gè)原方法塞一個(gè)動(dòng)態(tài)分配的可執(zhí)行地址 B。
當(dāng)執(zhí)行這個(gè)原方法的時(shí)候邪蛔,會(huì)跳轉(zhuǎn)到 可執(zhí)行地址 B急黎。
這個(gè) B 經(jīng)過(guò)一段簡(jiǎn)短的運(yùn)算操作,可以獲取到原先保存的 IMP A侧到。
在跳轉(zhuǎn)回 IMP A 之前勃教,統(tǒng)一攔截函數(shù)先做些事情,比如檢查是不是主線程調(diào)用之類的匠抗。但這里的這些操作時(shí)使用匯編來(lái)進(jìn)行故源,執(zhí)行效率更高。

示例代碼:

THInterceptor *interceptor = [THInterceptor sharedInterceptorWithFunction:(IMP)myInterceptor];
Method m = class_getInstanceMethod([UIView class], @selector(initWithFrame:));
IMP imp = method_getImplementation(m);
THInterceptorResult *interceptorResult = [interceptor interceptFunction:imp];
if (interceptorResult.state == THInterceptStateSuccess) {
    method_setImplementation(m, interceptorResult.replacedAddress); // 設(shè)置替換的地址
}
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; // 執(zhí)行到這一行時(shí)汞贸,會(huì)調(diào)用 myInterceptor 方法

使用場(chǎng)景:
目前作者只編寫arm64匯編代碼绳军,還不夠完善,不能在線上使用

參考文章:

iOS 上修改私有方法的幾種方式解析

基于橋的全量方法 Hook 方案

BlockHook with Struct


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矢腻,一起剝皮案震驚了整個(gè)濱河市门驾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踏堡,老刑警劉巖猎唁,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異顷蟆,居然都是意外死亡诫隅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門帐偎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逐纬,“玉大人,你說(shuō)我怎么就攤上這事削樊』砩” “怎么了兔毒?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)甸箱。 經(jīng)常有香客問(wèn)我育叁,道長(zhǎng),這世上最難降的妖魔是什么芍殖? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任豪嗽,我火速辦了婚禮,結(jié)果婚禮上豌骏,老公的妹妹穿的比我還像新娘龟梦。我一直安慰自己,他們只是感情好窃躲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布计贰。 她就那樣靜靜地躺著,像睡著了一般蒂窒。 火紅的嫁衣襯著肌膚如雪躁倒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天刘绣,我揣著相機(jī)與錄音樱溉,去河邊找鬼。 笑死纬凤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撩嚼。 我是一名探鬼主播停士,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼完丽!你這毒婦竟也來(lái)了恋技?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逻族,失蹤者是張志新(化名)和其女友劉穎蜻底,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聘鳞,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薄辅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抠璃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片站楚。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搏嗡,靈堂內(nèi)的尸體忽然破棺而出窿春,到底是詐尸還是另有隱情拉一,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布旧乞,位于F島的核電站蔚润,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏尺栖。R本人自食惡果不足惜抽碌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望决瞳。 院中可真熱鬧货徙,春花似錦、人聲如沸皮胡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)屡贺。三九已至蠢棱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甩栈,已是汗流浹背泻仙。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留量没,地道東北人玉转。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像殴蹄,于是被迫代替她去往敵國(guó)和親究抓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345