1. 程式人生 > >攔截任意object-c類的函式

攔截任意object-c類的函式

一種有缺點的方法

想攔截object-c類的函式,有一種方法是額外寫擴充套件類,例如下面程式碼:

@interface UIView(fordebug)
- (void)removeFromSuperview;
@end

@implementation UIView(fordebug)

- (void)removeFromSuperview{
    int abcd = 2134;
   // [super removeFromSuperview];
}

@end

以上程式碼丟到你的工程任意的m檔案中,就可以攔截UIView.removeFromSuperView函式的呼叫了。

但是!有一個缺點,

對於私有函式,hook的時候沒法呼叫原函式,例如上面程式碼的註釋行,編譯器會報錯,因為UIView.removeFromSuperView並沒有公開在UIView中,而是宣告在UIView(XXX)的擴充套件類中。

怎麼樣才能攔截任意類的函式呢?

徹底支援hook的方法

下面介紹另外一種方法。

1. 首先,新增一個新函式作為攔截時候相應的函式:

static void imp_processViewWillAppear(id self, SEL cmd, BOOL animated){
    
    //先執行原來的方法
    SEL oriSel = sel_getUid("hook_viewWillAppear:");
    void (*hook_viewWillAppear)(id, SEL, BOOL) = (void (*)(id,SEL,BOOL))[UIViewController instanceMethodForSelector:oriSel];//函式指標
    hook_viewWillAppear(self,cmd,animated);
    
    do sth。。。
}

 2. 然後讓被hook的函式指向新函式

methodExchange("UIViewController", "viewWillAppear:", "hook_viewWillAppear:", (IMP)imp_processViewWillAppear);

其中,methodExchange程式碼:


void methodExchange(const char *className, const char *originalMethodName, const char *replacementMethodName, IMP imp) {
    Class cls = objc_getClass(className);//得到指定類的類定義
    SEL oriSEL = sel_getUid(originalMethodName);//把originalMethodName註冊到RunTime系統中
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);//獲取例項方法
    struct objc_method_description *desc = method_getDescription(oriMethod);//獲得指定方法的描述
    assert(desc != NULL);
    if (desc->types) {
        SEL buSel = sel_registerName(replacementMethodName);//把replacementMethodName註冊到RunTime系統中
        
        if (class_addMethod(cls, buSel, imp, desc->types)) {//通過執行時,把方法動態新增到類中
            Method buMethod  = class_getInstanceMethod(cls, buSel);//獲取例項方法
            method_exchangeImplementations(oriMethod, buMethod);//交換方法
        }
    }
}

如此,就徹底實現了任意類的攔截響應。

有沒有更好的?

個人推理,以上兩種方法應該是可以結合使用,例如正常的對於私有的函式,再使用以下方法進行呼叫原函式:

@interface UIView(fordebug)
- (void)removeFromSuperview;
@end

@implementation UIView(fordebug)

- (void)removeFromSuperview{
    int abcd = 2134;

   // [super removeFromSuperview];

執行原來的方法
    SEL oriSel = sel_getUid("removeFromSuperview");
    void (*hook_removeFromSuperview)(id, SEL, BOOL) = (void (*)(id,SEL,BOOL))[UIView instanceMethodForSelector:oriSel];//函式指標
    hook_removeFromSuperview(self,cmd);
}

@end

不過這種方法沒驗證過,理論上評估是可以,讀者可以自行試試,

有問題,隨時聯絡,加扣扣群,或者留言。

 

 

本文結束。