问题描述:
笔者在实际开发中遇到了一个开发需求 就是给项目中所有的 UIControl控件(凡是能够通过 addTarget: 来添加交互的控件都是 UIControl控件)
比如按钮的 交互点击做全局埋点 也就是说要拦截按钮的点击活动. 然后也要提供一个参数 拦截到点击后是否需要将该埋点数据上报给后端.问题解决:
1.对于点击拦截问题 笔者提供了一个类拓展,直接将系统的sendAction:to:from:forEvent:
进行了替换
2.对于是否需要上报 笔者也是给NSObject 拓展了一个BOOL 属性(NSObject 是所有控件的最终父类 给NSObject 拓展属性 外界所有的控件都可以访问到
)
点击拦截
- 说明:
拦截所有 UIControl控件 的点击行为 --- 方法交换 给UIApplication 拓展方法
.m中
#import "UIApplication+Hook.h"
#import <objc/runtime.h> // 使用运行时需要包含此
#import "UIViewController+Extension.h" // 获取目前在栈顶的控制器 一般也就是目前正在显示的控制器
@implementation UIApplication (Hook)
#pragma mark - 点击拦截
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 获取原方法 及 新增方法的 函数名 (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)
SEL originalSelector = @selector(sendAction:to:from:forEvent:);
SEL swizzledSelector = @selector(wsd_sendAction:to:from:forEvent:);
[self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
});
}
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
Class class = cls;
// 获取原方法 及 新增方法的 函数实现 (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)
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);
}
}
- (BOOL)wsd_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
NSObject *senderObj = sender;
NSString *targetClassName = [NSString stringWithFormat:@"%@", [target class]];
// 拒绝所有关于 UITabBar 的点击响应的统计
if ([targetClassName isEqualToString:@"UITabBar"] || [targetClassName isEqualToString:@"TDAAUIControlBinding"] || [targetClassName isEqualToString:@"WSDTabBarViewController"]) {
}else{ // 加入统计
WSDLog(@"------------------ 监听到点击 ------------------- \n响应事件名称 = %@ ; \n第一响应者 = %@ ; \n第一响应者所在的控制器 = %@ \n是否需要上报 = %@",NSStringFromSelector(action),[target class] ,[[self.keyWindow.rootViewController wsd_topViewController] class],senderObj.wsd_haveAnalyzeBool? @"YES" : @"NO")
}
return [self wsd_sendAction:action to:target from:sender forEvent:event];
}
@end
解释:
// 获取原方法 及 新增方法的 函数名 (这里有个isa 指针 以及函数列表 和函数实现的知识 笔者在这里就不解释了)
- SEL originalSelector = @selector(sendAction:to:from:forEvent:);
// 获取原方法 及 新增方法的 函数实现
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
// 如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
- BOOL didAddMethod = class_addMethod()
// 如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是class_replaceMethod这个方法. class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation,所以直接调用class_replaceMethod就可以了)
- if (didAddMethod) {
class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
} else {// 如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即
method_exchangeImplementations(originalMethod, swizzledMethod);
}
该方法一定要写 不然外界拿不到点击事件 我们只是半路拦截 拦截之后 的事情还是交个系统处理
- return [self wsd_sendAction:action to:target from:sender forEvent:event];
全局新增Bool 属性 并设置初始值为YES
-
说明:
给NSObject 拓展一个全局的Bool类型的 属性
.h中
.m中
解释
- 大部分东西没什么可说的 就是通过运行时 拓展一个bool 属性 这里要说的是 如何实现Bool 的初始值为YES
关键代码 - 在你完成运行时代码之后 可能会收到一个关于这个的警告
Property '你的属性名' requires method '你的属性名:' to be defined - use @dynamic or provide a method implementation in this category
这是因为我们在类拓展中 增加属性时 系统是不会给我们自动生成setter方法和getter方法 在我们@property
新增属性时会有来个对应词 一个是@synthesize
,一个是@dynamic
如果@synthesize
和@dynamic
都没写,那么默认的就是@syntheszie var = _var
;
@synthesize
的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
@dynamic
告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)
假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;
或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃
编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定
消除警告
@dynamic 你增加的属性;
在.中就可以了
- 设置bool 默认值为YES
在实现getter方法中 直接使用以下代码就可以了
id objc = objc_getAssociatedObject(self, &增加该属性的静态key);
if (objc) return [objc boolValue];
self.新增属性= true; // 设置默认值为yes
return self.新增属性;