一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
方案二
描述
当一个视图被添加到父视图上时,系统会自 动调用-didMoveToSuperview方法。因此,我们可 以通过Method Swizzling交换UIView的- didMoveToSuperview方法,然后在交换方法里给 控件添加一组UIControlEventTouchDown类型的 Target-Action,并在Action里触发$AppClick事 件,从而实现$AppClick事件全埋点,这就是方案二的实现原理。
代码实现
新建一个UIControl的分类
UIControl+CountData.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIControl (CountData)
@end
NS_ASSUME_NONNULL_END
UIControl+CountData.m
+ (void)load {
[UIControl sensorsdata_swizzleMethod:@selector(didMoveToSuperview) withMethod:@selector(CountData_didMoveToSuperview)];
}
- (void)CountData_didMoveToSuperview {
//调用前交换原始方法
[self CountData_didMoveToSuperview];
[self addTarget:self action:@selector(CountData_touchDownAction:withEvent:) forControlEvents:UIControlEventTouchDown];
}
-(void)CountData_touchDownAction:(UIControl *)sender withEvent:(UIEvent *)event {
if ([self CountData_isAddMultipleTargetActionsWithDefaultEvent:UIControlEventTouchDown]) {
//触发$AppClick事件
UIView *view = (UIView *)sender;
NSMutableDictionary *prams = [[NSMutableDictionary alloc]init];
//获取控件类型
prams[@"$elementtype"] = view.elementType;
//获取控件的内容
prams[@"element_content"] = view.elementContent;
//获取所属的页面
UIViewController *vc = view.myViewController;
prams[@"element_screen"] = NSStringFromClass(vc.class);
[[SensorsAnalyticsSDK sharedInstance]track:@"appclick" properties:prams];
}
}
注意点:UIControl类中其实并没有实现-didMoveToSuperview方法,这个方法是 从它的父类UIView继承而来的。因此,我们实际上交换的是UIView中的- didMoveToSuperview方法。当UIView对象调用-didMoveToSuperview方法时,其实调用的是在UIControl+CountData.m中实现的- CountData_didMoveToSuperview方法。但是,UIView对象或者除了 UIControl类的其他UIView子类的对象,在执行-CountData_didMoveToSuperview方法时,并没有实现-CountData_didMoveToSuperview方法,因此,程序会出现 找不到方法而崩溃的情况。
针对这个问题,我们需要修改NSObject+SASwizzler.m文件中的 +sensorsdata_swizzleMethod:withMethod:类方法,即将其修改为:在方法交换之前,先在当前类中添加需要交换的方法,并在添加成功之后获取新的方法指针。
+ (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL {
//获取原始的方法
Method originalMethod = class_getInstanceMethod(self, originalSEL);
if (!originalMethod) {
return NO;
}
//获取将要交换的方法
Method alternateMethod = class_getInstanceMethod(self, alternateSEL);
if (!alternateMethod) {
return NO;
}
//获取originalSel方法实现
IMP originalIMP = method_getImplementation(originalMethod);
//获取originalSEL方法的类型
const char *originalMethodType = method_getTypeEncoding(originalMethod);
//往类中添加originalSEL方法,如果已经存在,则添加失败,并返回NO
if (class_addMethod(self, originalSEL, originalIMP, originalMethodType)) {
//如果添加成功,重新获取originalSEL实例方法
originalMethod = class_getInstanceMethod(self, originalSEL);
}
//获取alternateIMP方法实现
IMP alternateIMP = method_getImplementation(alternateMethod);
//获取alternateSEL方法的类型
const char *alternateMethodType = method_getTypeEncoding(alternateMethod);
//往类中添加alternateSEL方法,如果已经存在,则添加失败,并返回NO
if (class_addMethod(self, alternateSEL, alternateIMP, alternateMethodType)) {
//如果添加成功,重新获取alternateSEL实例方法
alternateMethod = class_getInstanceMethod(self, alternateSEL);
}
//交互两个方法的实现
method_exchangeImplementations(originalMethod, alternateMethod);
//返回yes,方法交换成功
return YES;
}