iOS開發(fā)中實現(xiàn)hook消息機制的方法探究
Method Swizzling 原理
在Objective-C中調(diào)用一個方法,其實是向一個對象發(fā)送消息,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動態(tài)特性,可以實現(xiàn)在運行時偷換selector對應(yīng)的方法實現(xiàn),達(dá)到給方法掛鉤的目的。
每個類都有一個方法列表,存放著selector的名字和方法實現(xiàn)的映射關(guān)系。IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn)。
我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP,
我們可以利用 class_replaceMethod 來修改類,
我們可以利用 method_setImplementation 來直接設(shè)置某個方法的IMP,
……
歸根結(jié)底,都是偷換了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
乍一看,這不遞歸了么?別忘記這是我們準(zhǔn)備調(diào)換IMP的selector,[self myLastObject] 將會執(zhí)行真的 [self lastObject] 。
第二步:調(diào)換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
結(jié)果很讓人欣喜,是不是忍不住想給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對應(yīng)的method
Method sendEvent = class_getInstanceMethod([UIWindow class], @selector(sendEvent:));
Method sendEventMySelf = class_getInstanceMethod([self class], @selector(sendEventHooked:));
// 將目標(biāo)函數(shù)的原實現(xiàn)綁定到sendEventOriginalImplemention方法上
IMP sendEventImp = method_getImplementation(sendEvent);
class_addMethod([UIWindow class], @selector(sendEventOriginal:), sendEventImp, method_getTypeEncoding(sendEvent));
// 然后用我們自己的函數(shù)的實現(xiàn),替換目標(biāo)函數(shù)對應(yīng)的實現(xiàn)
IMP sendEventMySelfImp = method_getImplementation(sendEventMySelf);
class_replaceMethod([UIWindow class], @selector(sendEvent:), sendEventMySelfImp, method_getTypeEncoding(sendEvent));
}
/*
* 截獲到window的sendEvent
* 我們可以先處理完以后,再繼續(xù)調(diào)用正常處理流程
*/
- (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(一個結(jié)構(gòu)體,用來對方法進行描述),接著第20行是獲取到我們自己定義的類中的sendEvent的Method(這兩個方法的簽名必須一樣,否則運行時報錯)。第23行我們通過UIWindow原生的sendEvent的Method獲取到對應(yīng)的IMP(一個函數(shù)指針),第24行使用運行時API Class_addMethod給UIWindow類添加了一個叫sendEventOriginal的方法,該方法使用UIWindow原生的sendEvent的實現(xiàn),并且有著相同的方法簽名(必須相同,否則運行時報錯)。27行是獲取我們自定義類中的sendEventMySelf的IMP,28行是關(guān)鍵的一行,這一行的主要目的是為UIWindow原生的sendEvent指定一個新的實現(xiàn),我們看到我們將該實現(xiàn)指定到了我們自己定義的sendEventMySelf上。到了這兒我們就完成了偷梁換柱,大功告成。
執(zhí)行上面這些行以后,我們就成功的將UIWindow的sendEvent重定向到了我們自己的寫的sendEventMySelf的實現(xiàn),然后將其原本的實現(xiàn)重定向到了我們給它新添加的方法sendEventOriginal中。而sendEventMySelf中,我們首先可以對這個消息進行我們想要的處理,然后再通過41行調(diào)用sendEventOriginal方法轉(zhuǎn)到正常的執(zhí)行流程。
這塊兒你可能有個困惑 “我們自定義類中明明是沒有sendEventOriginal方法的啊?”
為什么執(zhí)行起來不報錯,而且還會正常執(zhí)行?因為sendEventMySelf是UIWindow的sendEvent重定向過來的,所以在運行時該方法中的self代表的就是UIWindow的實例,而不再是TestHookObject的實例了。加上sendEventOriginal是我們通過運行時添加到UIWindow的實例方法,所以可以正常調(diào)用。當(dāng)然如果直接通過下面這種方式調(diào)用也是可以的,只不過編譯器會提示警告(編譯器沒那么智能),因此我們采用了performSelector的調(diào)用方式。
[self sendEventOriginal:event];
以上就是Hook的實現(xiàn),使用時我們只需要讓TestHookObject類執(zhí)行一次初始話操作就可以了,執(zhí)行完以后。UIWindow的sendEvent消息就會會hook到我們的sendEventMySelf中了。
下面是調(diào)用代碼:
- (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完以后消息是否正常傳遞。經(jīng)驗證消息流轉(zhuǎn)完全正常。
相關(guān)文章
解決iOS7上UITextField限制字?jǐn)?shù)輸入導(dǎo)致崩潰問題的方法
這篇文章主要為大家分享了解決iOS7上UITextField限制字?jǐn)?shù)輸入導(dǎo)致崩潰問題的方法,感興趣的小伙伴們可以參考一下2016-03-03iOS如何獲取屏幕寬高、設(shè)備型號、系統(tǒng)版本信息
這篇文章主要介紹了iOS如何獲取屏幕寬高、設(shè)備型號、系統(tǒng)版本信息的相關(guān)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Objective-C中利用正則去除非數(shù)字字母漢字方法實例
正則表達(dá)式對我們?nèi)粘i_發(fā)來說是必不可少的,下面這篇文章主要給大家介紹了關(guān)于Objective-C中如何利用正則去除非數(shù)字字母漢字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06iOS實現(xiàn)應(yīng)用內(nèi)切換語言及字體大?。7挛⑿牛?/a>
這篇文章主要給大家介紹了關(guān)于利用iOS如何實現(xiàn)應(yīng)用內(nèi)切換語言及字體大小的相關(guān)資料,實現(xiàn)的效果類似我們經(jīng)常在微信中見到的,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01判斷iOS應(yīng)用是否開放HTTP權(quán)限的方法
這篇文章主要為大家詳細(xì)介紹了判斷iOS應(yīng)用是否開放HTTP權(quán)限的方法,感興趣的小伙伴們可以參考一下2016-03-03