第一种方式就是业务代码与统计代码相分离,利用runTime的特性,具体操作如下
定义工具类
@interface WHookUtility : NSObject
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector;
@end
#import
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
Class class = cls;
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
对于以上的函数需要有以下说明:
1 上面的函数主要作用就是交换两个方法的实现,主要的三句代码是:
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
让我举个栗子��:比如你需要统计一个viewController的生命周期开始和结束,那一搬情况下是不是需要在它的
viewWillAppear viewDidDisappear里面写上一句监听的代码[UMXXXX trackEvent:@"EventName"]
如果在每个页面里面都这样写,那么统计这一块的代码耦合很高,这样很不好。现在有一个解决方法,利用RunTime特性Hook住
viewWillAppear viewDidDisappear 这两个函数。具体实现原理看以下UIViewController+userStatics.h
的demo。
2 除了函数的主要作用的三句代码那class_addMethod class_replaceMethod 的作用是什么呢
假如你hook的函数并不存在比如随意一个函数viewWillShow 那么这两句代码就稍微有点有作用了,至少你在你hook的
那个类里面执行[self performSelector:@selector(viewWillShow) withObject:nil]不会崩溃。
(前提是你已经定义好了交换的函数哦)。总的来说只要你不莫名其妙的写出hook不存在的函数,这两个函数是没用的。
即使hook了不存在的函数也不会崩溃的。如果你发现这两句代码还有其它的作用,可以交流一下~
现在我们来使用一下以上的类
建一个基于UIViewController的类别
#import "UIViewController+userStatics.h"
#import "WHookUtility.h"
@implementation UIViewController (userStatics)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(swiz_viewWillAppear:);
[WHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
SEL originalSelectorDis = @selector(viewWillDisappear:);
SEL swizzledSelectorDis = @selector(swiz_viewWillDisappear:);
[WHookUtility swizzlingInClass:[self class] originalSelector:originalSelectorDis swizzledSelector:swizzledSelectorDis];
});
}
#pragma mark - Method Swizzling
swiz_viewWillAppear:(BOOL)animated swiz_viewWillDisappear:(BOOL)animated 是我自己在类别里面定义的函数,
在以上的load函数里面我将它们与 viewWillAppear: viewWillDisappear: 交换了。所以当一个类里面执行viewWillAppear:
它事实上执行的是函数swiz_viewWillAppear:(BOOL)animated 你会发现在函数swiz_viewWillAppear:(BOOL)animated
里面有句代码 [self swiz_viewWillAppear:animated]; 由于交换的原因它事实上执行的是函数viewWillAppear:。
- (void)swiz_viewWillAppear:(BOOL)animated
{
//插入需要执行的代码
NSLog(@"我在viewWillAppear执行前偷偷插入了一段代码%@",[self class]);
//不能干扰原来的代码流程,插入代码结束后要让本来该执行的代码继续执行
[self swiz_viewWillAppear:animated];
}
- (void)swiz_viewWillDisappear:(BOOL)animated
{
//插入需要执行的代码
NSString *pageName=NSStringFromClass([self class]);
NSLog(@"结束监听%@",pageName);
//不能干扰原来的代码流程,插入代码结束后要让本来该执行的代码继续执行
[self swiz_viewWillDisappear:animated];
}
#import "UIControl+userStatics.h"
#import "WHookUtility.h"
@implementation UIControl (userStatics)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(sendAction:to:forEvent:);
SEL swizzledSelector = @selector(swiz_sendAction:to:forEvent:);
[WHookUtility swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
});
}
#pragma mark - Method Swizzling
- (void)swiz_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event;
{
//插入埋点代码
[self performUserStastisticsAction:action to:target forEvent:event];
[self swiz_sendAction:action to:target forEvent:event];
}
- (void)performUserStastisticsAction:(SEL)action to:(id)target forEvent:(UIEvent *)event;
{
NSLog(@"\n***hook success.\n[1]action:%@\n[2]target:%@ \n[3]event:%ld", NSStringFromSelector(action), target, (long)event);
}
但是这种方法有一个突出的问题解决不了:无法交换代理函数
你如果想要监听webview的代理函数,tableView的代理函数那就无法用这种方法了。