iOS開發中實現hook訊息機制的方法探究_IOS

來源:互聯網
上載者:User

Method Swizzling 原理

在Objective-C中調用一個方法,其實是向一個對象發送訊息,尋找訊息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鈎的目的。
每個類都有一個方法列表,存放著selector的名字和方法實現的映射關係。IMP有點類似函數指標,指向具體的Method實現。

我們可以利用 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] 將會執行真的 [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:

複製代碼 代碼如下:

2013-07-18 16:26:12.585 Hook[1740:c07] **********  myLastObject ***********  
2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3 


結果很讓人欣喜,是不是忍不住想給UIWebView的loadRequest: 加 TODO 了呢?

樣本

有了這個原理,接下來讓我們來看一個執行個體:
下面先直接上源碼:
 

複製代碼 代碼如下:

//
//  TestHookObject.m
//  TestHookMessage
//
//  Created by mapleCao on 13-2-28.
//  Copyright (c) 2013年 mapleCao. All rights reserved.
//

#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:));
   
    // 將目標函數的原實現綁定到sendEventOriginalImplemention方法上
    IMP sendEventImp = method_getImplementation(sendEvent);
    class_addMethod([UIWindow class], @selector(sendEventOriginal:), sendEventImp, method_getTypeEncoding(sendEvent));
   
    // 然後用我們自己的函數的實現,替換目標函數對應的實現
    IMP sendEventMySelfImp = method_getImplementation(sendEventMySelf);
    class_replaceMethod([UIWindow class], @selector(sendEvent:), sendEventMySelfImp, method_getTypeEncoding(sendEvent));
}

/*
 * 截獲到window的sendEvent
 * 我們可以先處理完以後,再繼續調用正常處理流程
 */
- (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的實現,並且有著相同的方法簽名(必須相同,否則運行時報錯)。27行是擷取我們自訂類中的sendEventMySelf的IMP,28行是關鍵的一行,這一行的主要目的是為UIWindow原生的sendEvent指定一個新的實現,我們看到我們將該實現指定到了我們自己定義的sendEventMySelf上。到了這兒我們就完成了偷梁換柱,大功告成。
 
  執行上面這些行以後,我們就成功的將UIWindow的sendEvent重新導向到了我們自己的寫的sendEventMySelf的實現,然後將其原本的實現重新導向到了我們給它新添加的方法sendEventOriginal中。而sendEventMySelf中,我們首先可以對這個訊息進行我們想要的處理,然後再通過41行調用sendEventOriginal方法轉到正常的執行流程。
 
  這塊兒你可能有個困惑 “我們自訂類中明明是沒有sendEventOriginal方法的啊?”
 
  為什麼執行起來不報錯,而且還會正常執行?因為sendEventMySelf是UIWindow的sendEvent重新導向過來的,所以在運行時該方法中的self代表的就是UIWindow的執行個體,而不再是TestHookObject的執行個體了。加上sendEventOriginal是我們通過運行時添加到UIWindow的執行個體方法,所以可以正常調用。當然如果直接通過下面這種方式調用也是可以的,只不過編譯器會提示警告(編譯器沒那麼智能),因此我們採用了performSelector的調用方式。
 

複製代碼 代碼如下:

[self sendEventOriginal:event];

  以上就是Hook的實現,使用時我們只需要讓TestHookObject類執行一次初始話操作就可以了,執行完以後。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完以後訊息是否正常傳遞。經驗證訊息流程轉完全正常。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.