Method Swizzling 原理
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_html" target="_blank">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];
- (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完以后消息是否正常传递。经验证消息流转完全正常。
本文向大家介绍iOS开发系列--通知与消息机制详解,包括了iOS开发系列--通知与消息机制详解的使用技巧和注意事项,需要的朋友参考一下 概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情。iOS中通知机制又叫消息机制,其包括两类:一类是本地通知;另一类是推送通知,也叫远程通知。两种通知在iOS中的表现一
本文向大家介绍.net平台推送ios消息的实现方法,包括了.net平台推送ios消息的实现方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了.net平台推送ios消息的实现方法。分享给大家供大家参考。 具体实现步骤如下: 1、ios应用程序中允许向客户推送消息 2、需要有苹果的证书以及密码(怎么获取,网上搜一下,需要交费的) 3、iphone手机一部,安装了该ios应用程序 4、.net
本文向大家介绍Android编程实现异步消息处理机制的几种方法总结,包括了Android编程实现异步消息处理机制的几种方法总结的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Android编程实现异步消息处理机制的几种方法。分享给大家供大家参考,具体如下: 1、概述 Android需要更新ui的话就必须在ui线程上进行操作。否则就会抛异常。 假如有耗时操作,比如:在子线程中下载文件,通知u
本文向大家介绍iOS开发中音频视频播放的简单实现方法,包括了iOS开发中音频视频播放的简单实现方法的使用技巧和注意事项,需要的朋友参考一下 前言 我们在平时的iOS开发中,音视频的播放有很多种,目前系统的自带的都属于 AVFoundation 框架,更加接近于底层,所以灵活性很强,更加方便自定义 还有就是第三方音视频视频播放,特点是功能强大,实现简单,支持流媒体,下面来逐一介绍,给大家参考学习,下
本文向大家介绍iOS绘制3D饼图的实现方法,包括了iOS绘制3D饼图的实现方法的使用技巧和注意事项,需要的朋友参考一下 实现核心 1.压缩饼图,使饼图有3D的效果,并不是真正的画了个3D圆柱 2.绘制厚度,带阴影效果,让看上去像是圆柱的高 3.路径添加好了,用颜色填充后绘制一下,添加阴影后还需绘制一遍 饼图添加阴影的思考 之前这加阴影的一段不是很明白,为啥设颜色和阴影
本文向大家介绍MFC自定义消息的实现方法,包括了MFC自定义消息的实现方法的使用技巧和注意事项,需要的朋友参考一下 一、概述: 消息机制是windows程序的典型运行机制,在MFC中有很多已经封装好了的消息,如WM_BTN**等。但是在有些特殊情况下我们需要自定义一些消息去完成一些我们所需要的功能,这时候MFC的向导不能帮助我们做到这一点。对此,我们可以通过添加相应的代码去完成这个功能。 二、实现