iOS開發(fā)中實現(xiàn)hook消息機制的方法探究

Method Swizzling 原理
在Objective-C中調用一個方法,其實是向一個對象發(fā)送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態(tài)特性,可以實現(xiàn)在運行時偷換selector對應的方法實現(xiàn),達到給方法掛鉤的目的。
每個類都有一個方法列表,存放著selector的名字和方法實現(xiàn)的映射關系。IMP有點類似函數指針,指向具體的Method實現(xiàn)。



我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP,
我們可以利用 class_replaceMethod 來修改類,
我們可以利用 method_setImplementation 來直接設置某個方法的IMP,
……
歸根結底,都是偷換了selector的IMP,如下圖所示:


Method Swizzling 實踐

舉個例子好了,我想鉤一下NSArray的lastObject 方法,只需兩個步驟。
第一步:給NSArray加一個我自己的lastObject

#import "NSArray+Swizzle.h"  
@implementation NSArray (Swizzle)  
- (id)myLastObject  {  
    id ret = [self myLastObject];  
    NSLog(@"**********  myLastObject *********** ");  
    return ret;  
}  
@end 

乍一看,這不遞歸了么?別忘記這是我們準備調換IMP的selector,[self myLastObject] 將會執(zhí)行真的 [self lastObject] 。

第二步:調換IMP

#import   
#import "NSArray+Swizzle.h"  
int main(int argc, char *argv[])  
{  
    @autoreleasepool {  
        Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));  
        Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));  
        method_exchangeImplementations(ori_Method, my_Method);  
          
        NSArray *array = @[@"0",@"1",@"2",@"3"];  
        NSString *string = [array lastObject];  
        NSLog(@"TEST RESULT : %@",string);  
        return 0;  
    }  
} 

控制臺輸出Log:

**********  myLastObject ***********   
TEST RESULT : 3 

結果很讓人欣喜,是不是忍不住想給UIWebView的loadRequest: 加 TODO 了呢?
示例
有了這個原理,接下來讓我們來看一個實例:
下面先直接上源碼:

#import "TestHookObject.h"
#import <objc/objc.h>
#import <objc/runtime.h>
@implementation TestHookObject
// this method will just excute once
+ (void)initialize
{
    // 獲取到UIWindow中sendEvent對應的method
    Method sendEvent = class_getInstanceMethod([UIWindow class], @selector(sendEvent:));
    Method sendEventMySelf = class_getInstanceMethod([self class], @selector(sendEventHooked:));
    
    // 將目標函數的原實現(xiàn)綁定到sendEventOriginalImplemention方法上
    IMP sendEventImp = method_getImplementation(sendEvent);
    class_addMethod([UIWindow class], @selector(sendEventOriginal:), sendEventImp, method_getTypeEncoding(sendEvent));
    
    // 然后用我們自己的函數的實現(xiàn),替換目標函數對應的實現(xiàn)
    IMP sendEventMySelfImp = method_getImplementation(sendEventMySelf);
    class_replaceMethod([UIWindow class], @selector(sendEvent:), sendEventMySelfImp, method_getTypeEncoding(sendEvent));
}
/*
 * 截獲到window的sendEvent
 * 我們可以先處理完以后,再繼續(xù)調用正常處理流程
 */
- (void)sendEventHooked:(UIEvent *)event
{
    // do something what ever you want
    NSLog(@"haha, this is my self sendEventMethod!!!!!!!");
    
    // invoke original implemention
    [self performSelector:@selector(sendEventOriginal:) withObject:event];
}
@end

下面我們來逐行分析一下上面的代碼:

首先我們來看19行,這一行主要目的是獲取到UIWindow原生的sendEvent的Method(一個結構體,用來對方法進行描述),接著第20行是獲取到我們自己定義的類中的sendEvent的Method(這兩個方法的簽名必須一樣,否則運行時報錯)。第23行我們通過UIWindow原生的sendEvent的Method獲取到對應的IMP(一個函數指針),第24行使用運行時API Class_addMethod給UIWindow類添加了一個叫sendEventOriginal的方法,該方法使用UIWindow原生的sendEvent的實現(xiàn),并且有著相同的方法簽名(必須相同,否則運行時報錯)。27行是獲取我們自定義類中的sendEventMySelf的IMP,28行是關鍵的一行,這一行的主要目的是為UIWindow原生的sendEvent指定一個新的實現(xiàn),我們看到我們將該實現(xiàn)指定到了我們自己定義的sendEventMySelf上。到了這兒我們就完成了偷梁換柱,大功告成。

執(zhí)行上面這些行以后,我們就成功的將UIWindow的sendEvent重定向到了我們自己的寫的sendEventMySelf的實現(xiàn),然后將其原本的實現(xiàn)重定向到了我們給它新添加的方法sendEventOriginal中。而sendEventMySelf中,我們首先可以對這個消息進行我們想要的處理,然后再通過41行調用sendEventOriginal方法轉到正常的執(zhí)行流程。

這塊兒你可能有個困惑 “我們自定義類中明明是沒有sendEventOriginal方法的?。俊?/p>

為什么執(zhí)行起來不報錯,而且還會正常執(zhí)行?因為sendEventMySelf是UIWindow的sendEvent重定向過來的,所以在運行時該方法中的self代表的就是UIWindow的實例,而不再是TestHookObject的實例了。加上sendEventOriginal是我們通過運行時添加到UIWindow的實例方法,所以可以正常調用。當然如果直接通過下面這種方式調用也是可以的,只不過編譯器會提示警告(編譯器沒那么智能),因此我們采用了performSelector的調用方式。

[self sendEventOriginal:event];

以上就是Hook的實現(xiàn),使用時我們只需要讓TestHookObject類執(zhí)行一次初始話操作就可以了,執(zhí)行完以后。UIWindow的sendEvent消息就會會hook到我們的sendEventMySelf中了。

下面是調用代碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[TestHookViewController alloc] initWithNibName:@"TestHookViewController" bundle:nil] autorelease];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    
    
    //hook UIWindow‘s SendEvent method
    TestHookObject *hookSendEvent = [[TestHookObject alloc] init];
    [hookSendEvent release];
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    btn.center = CGPointMake(160, 240);
    btn.backgroundColor = [UIColor redColor];
    [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventAllEvents];
    [self.window addSubview:btn];
    [btn release];
    
    return YES;
}

代碼中我們還專門添加了一個button來驗證,hook完以后消息是否正常傳遞。經驗證消息流轉完全正常。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 轉至元數據結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 2,049評論 0 9
  • 消息發(fā)送和轉發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,986評論 2 3
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,333評論 0 7
  • 1、禁止手機睡眠[UIApplication sharedApplication].idleTimerDisabl...
    DingGa閱讀 1,206評論 1 6
  • 中午,我和媽媽送哥哥上作文課的啦?;貋頃r,我媽媽她還上超市給我買了許多好吃的,我媽媽還給我買了個玩具,還給我買了三...
    邸廣碩閱讀 177評論 0 1

友情鏈接更多精彩內容