iOS开发中实现hook消息机制的方法探究

2016-02-19 10:55 60 1 收藏

下面这个iOS开发中实现hook消息机制的方法探究教程由图老师小编精心推荐选出,过程简单易学超容易上手,喜欢就要赶紧get起来哦!

【 tulaoshi.com - 编程语言 】

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 了呢?

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

示例

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

有了这个原理,接下来让我们来看一个实例:
下面先直接上源码:
 

代码如下:

//
//  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完以后消息是否正常传递。经验证消息流转完全正常。

来源:https://www.tulaoshi.com/n/20160219/1596101.html

延伸阅读
iOS沙盒机制  iOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。 每个应用程序都有自己的存储空间 应用程序不能翻过自己的围墙去访问别的存储空间的内容 打开模拟器沙盒目录 方法1、可以设置...
音效的播放 一、简单介绍 简单来说,音频可以分为2种 (1)音效 又称“短音频”,通常在程序中的播放时长为1~2秒 在应用程序中起到点缀效果,提升整体用户体验 (2)音乐 比如游戏中的“背景音乐”,一般播放时间较长 框架:播放音频需要用到AVFoundation.framework框架 二、音效的播放 1.获得音效文件的路径 代码如下: ...
在iOS5.1 和 之前的版本中, 我们通常利用 shouldAutorotateToInterfaceOrientation: 来单独控制某个UIViewController的旋屏方向支持,比如: 代码如下: - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation  {      return (interfaceOrientation == UIInterfaceOrie...
实现UItableview控件数据刷新 一、项目文件结构和plist文件 二、实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三、代码示例 数据模型部分: YYheros.h文件 代码如下: // //  YYheros.h //  10-英...
一、简单介绍 1.什么是UIPopoverController 是iPad开发中常见的一种控制器(在iPhone上不允许使用) 跟其他控制器不一样的是,它直接继承自NSObject,并非继承自UIViewController 它只占用部分屏幕空间来呈现信息,而且显示在屏幕的最前面 2.使用步骤 要想显示一个UIPopoverController,需要经过下列步骤 (1)设置内容控制器 ...

经验教程

753

收藏

13
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部