废话不多说。直接上代码。
#import <Foundation/Foundation.h>
@interface HookObject :NSObject
//单位分钟
(nonatomic)NSInteger timeout;
@end
#import "HookObject.h"
#import <objc/objc.h>
#import <objc/runtime.h>
HookObject ()
(nonatomic, strong)NSTimer *timer;
@end
HookObject
"kCaptureTouch"
3 //单位分钟
- (id)init
{
self = [super init];
if (self)
{
// 获取到UIWindow中sendEvent对应的method
/**
先获取UIWindow中sendEvent:(事件分发)的方法
再获取我们要覆盖sendEvent:方法的方法在self的sendEventMySelf
**/
Method sendEvent = class_getInstanceMethod([UIWindow class], @selector(sendEvent:));
Method sendEventMySelf = class_getInstanceMethod([self class], @selector(sendEventHooked:));
// 将目标函数的原实现绑定到sendEventOriginal方法上
/**
实现的步骤是:先获取sendEvent的方法实现
为[UIWindow class]添加sendEventOriginal:方法,该方法的实现和
是一样的,并且获取sendEvend的参数和返回类型并添加到
:中去
**/
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));
//注册捕获到触摸的通知
NSNotificationCenter defaultCenter] addObserver:self selector:@selector(captureTouchNotification) name:kCaptureTouch object:nil];
_timeout = kDefaultTimeOut;
}
return self;
}
//捕获到触摸的通知回调方法
- (void)captureTouchNotification
{
NSLog(@"捕获到触摸");
//固定套路,发消息通知取消播放屏保,然后重新设置定时器。外层做判断,如果本身没在播放,则不处理此消息即可。
[[NSNotificationCenter defaultCenter] postNotificationName:kTimeOutStopVideo object:nil];
[self.timer invalidate];
self.timer = nil;
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeout*60 target:self selector:@selector(touchTimeOutAction) userInfo:nil repeats:NO];
}
//触摸超时回调方法
- (void)touchTimeOutAction
{
NSLog(@"触摸超时,开始屏保");
//固定套路,发消息通知开始播放屏保
[[NSNotificationCenter defaultCenter] postNotificationName:kTimeOutPlayVideo object:nil];
}
//重写设置器,重新设置定时器
- (void)setTimeout:(NSInteger)timeout
{
NSLog(@"重新设置超时时间");
_timeout
[self.timer invalidate];
self.timer = nil;
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeout*60 target:self selector:@selector(touchTimeOutAction) userInfo:nil repeats:NO];
}
/*
截获到window的sendEvent
* 我们可以先处理完以后,再继续调用正常处理流程
*/
- (void)sendEventHooked:(UIEvent
{
//捕获到触摸事件则开始计时,这里不能用self去访问 HookObject 当前这个类的类变量,原因如下说明,而应该发通知去通知这个类
if (event.type == UIEventTypeTouches)
{
//至少有开始和结束2中状态,判断一种就行,否则会发送多个通知
if ([[[event allTouches] anyObject] phase] == UITouchPhaseBegan)
{
[[NSNotificationCenter defaultCenter] postNotificationName:kCaptureTouch object:nil];
}
}
//这个是UIWindow类的方法指定过来的,所以这里的self就不是HookObject的实例了,而是执行的UIWindow实例
self performSelector:@selector(sendEventOriginal:) withObject:event];
}
//移除观察者
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
方法一,hook已有公开头文件的类:
首先写一个Utility函数:
[cpp] view plain copy
1. #import <objc/runtime.h>
2. inline void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL)
3. {
4. Method oldMethod = class_getInstanceMethod(aClass, oldSEL);
5. assert(oldMethod);
6. Method newMethod = class_getInstanceMethod(aClass, newSEL);
7. assert(newMethod);
8. method_exchangeImplementations(oldMethod, newMethod);
9. }
现在,目标是hook UIWebView没公开的函数
[cpp] view plain copy
- - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
因为已知类的声明,所以可以使用category:
[cpp] view plain copy
1. @interface UIWebView (Hook)
2. + (void)hook;
3. - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
4. @end
5. @implementation UIWebView (Hook)
6. + (void)hook
7. {
8. // hook UIWebView中表示一个HTML的frame加载完毕的函数
9. exchangeMethod([UIWebViewclass],
10. @selector(webView:didFinishLoadForFrame:),
11. @selector(hook_webView:didFinishLoadForFrame:));
12. }
13. - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2
14. {
15. // 因为交换了selector和implementation的映射,原样调一下本函数实际会调用被hook的函数。
16. [self hook_webView:arg1 didFinishLoadForFrame:arg2];
17. "webView:didFinishLoadForFrame:");
18. }
在程序启动的时候调用一下 [UIWebView hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
方法二,hook没有公开头文件的类,需要另建一个类作为新函数载体,然后先为被hook的类增加函数,再替换:
UIWebView体系中有一个类叫UIWebBrowserView,它是真正显示网页的UIView,并有部分函数是作为WebCore向外发送回调信息的接收者。
现我们去hook UIWebBrowserView的这个函数:
[cpp] view plain copy
- - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
嗯,是的,这个函数和UIWebView的一样,实际上就是UIWebBrowserView会再调用通知到UIWebView的同名函数。
创建一个类,不要与被hook的类同名,例如加了个Hook后缀:
[cpp] view plain copy
1. @interface UIWebBrowserViewHook : NSObject
2. + (void)hook;
3. - (void)hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
4. @end
其中以hook_为前缀的新增函数的实现与方法一中相同,差别在类函数中:
[cpp] view plain copy
1. @implementation UIWebBrowserViewHook
2. + (void)hook
3. {
4. "UIWebBrowserView");
5. SEL sel = @selector(hook_webView:didFinishLoadForFrame:);
6. // 为UIWebBrowserView增加函数
7. class], sel), "v@:@@");
8. // 交换实现
9. exchangeMethod(aClass, @selector(webView:didFinishLoadForFrame:), sel);
10. }
在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
方法三,hook没有公开头文件的类,另建一个类作为新函数载体,用新函数替换旧函数,并把旧函数保存到静态变量里:
继续以UIWebBrowserView为例子。注意新函数可以与被hook的函数同名
[cpp] view plain copy
1. @interface UIWebBrowserViewHook : NSObject
2. + (void)hook;
3. - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
4. @end
需要用到另一个Utility函数:
[cpp] view plain copy
1. inline void replaceImplementation(Class newClass, Class hookedClass, SEL sel, IMP& oldImp)
2. {
3. Method old = class_getInstanceMethod(hookedClass, sel);
4. IMP newImp = class_getMethodImplementation(newClass, sel);
5. oldImp =
method_setImplementation(old, newImp);
- }
当两个selector不同名时,以上函数再增加一个参数即可。
下面是实现:
[cpp] view plain copy
1. @implementation UIWebBrowserViewHook
2. static IMP webView_didFinishLoadForFrame = NULL;
3. + (void)hook
4. {
5. "UIWebBrowserView");
6. SEL sel = @selector(webView:didFinishLoadForFrame:);
7. class], hookedClass, sel, webView_didFinishLoadForFrame);
8. }
9.
10. - (void)webView:(id)arg1 didFinishLoadForFrame:(id)arg2
11. {
12. // 需要这样来调用被替换掉的原实现
13. webView_didFinishLoadForFrame(self, @selector(webView:didFinishLoadForFrame:), arg1, arg2);
14. "webView:didFinishLoadForFrame:");
15. }
16. @end
在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。
三种方法的比较:
最方便的当然是第一种,但需要是hook有公开头文件的类。
方法二和方法三都新建了一个类,方法二需要描述selector的types,这个比较麻烦,如例子中的"v@:@@"表示返回值为void,第一和第二个参数都是id。方法三不用types,但要增加全局变量。