有目标总比没目标好,如果总是想,而不去付出实践,不去动手,可能永远都不懂吧。
UITableView如何进行无痕埋点呢?
首先需要对setDelegate进行方法交换。
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalAppearSelector = @selector(setDelegate:);
SEL swizzingAppearSelector = @selector(user_setDelegate:);
[MethodSwizzingTool swizzingForClass:[self class] originalSel:originalAppearSelector swizzingSel:swizzingAppearSelector];
});
}
然后我们拿到当前Viewcontroller里面的tableview,然后根据tag找到是哪个tableview.
通过class_addMethod添加这个方法的实现,user_tableView:didSelectRowAtIndexPath:。
然后再与tableView:didSelectRowAtIndexPath进行交换方法
//判断页面是否实现了某个sel
- (BOOL)isContainSel:(SEL)sel inClass:(Class)class {
unsigned int count;
Method *methodList = class_copyMethodList(class,&count);
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *tempMethodString = [NSString stringWithUTF8String:sel_getName(method_getName(method))];
if ([tempMethodString isEqualToString:NSStringFromSelector(sel)]) {
return YES;
}
}
return NO;
}
-(void)user_setDelegate:(id<UITableViewDelegate>)delegate
{
[self user_setDelegate:delegate];
SEL sel = @selector(tableView:didSelectRowAtIndexPath:);
SEL sel_ = NSSelectorFromString([NSString stringWithFormat:@"%@/%@/%ld", NSStringFromClass([delegate class]), NSStringFromClass([self class]),self.tag]);
//因为 tableView:didSelectRowAtIndexPath:方法是optional的,所以没有实现的时候直接return
if (![self isContainSel:sel inClass:[delegate class]]) {
return;
}
BOOL addsuccess = class_addMethod([delegate class],
sel_,
method_getImplementation(class_getInstanceMethod([self class], @selector(user_tableView:didSelectRowAtIndexPath:))),
nil);
//如果添加成功了就直接交换实现, 如果没有添加成功,说明之前已经添加过并交换过实现了
if (addsuccess) {
Method selMethod = class_getInstanceMethod([delegate class], sel);
Method sel_Method = class_getInstanceMethod([delegate class], sel_);
method_exchangeImplementations(selMethod, sel_Method);
}
}
后面我们同样,先通过runtime来调用一下ViewController。
SEL sel = NSSelectorFromString([NSString stringWithFormat:@"%@/%@/%ld", NSStringFromClass([self class]), NSStringFromClass([tableView class]), tableView.tag]);
if ([self respondsToSelector:sel]) {
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL,id,id) = (void *)imp;
func(self, sel,tableView,indexPath);
}
看一下一会要用的埋点结构
"TABLEVIEW": {
"ViewController/TestTableview/0":{
"userDefined": {
"eventid": "201803074|93",
"target": "",
"pageid": "234",
"pagename": "tableview 被点击"
},
"pagePara": {
"user_grade": {
"propertyName": "grade",
"propertyPath":"",
"containIn": "1"
}
}
}
},
// 由于我们交换了方法, 所以在tableview的 didselected 被调用的时候, 实质调用的是以下方法:
-(void)user_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SEL sel = NSSelectorFromString([NSString stringWithFormat:@"%@/%@/%ld", NSStringFromClass([self class]), NSStringFromClass([tableView class]), tableView.tag]);
if ([self respondsToSelector:sel]) {
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL,id,id) = (void *)imp;
func(self, sel,tableView,indexPath);
}
NSString * identifier = [NSString stringWithFormat:@"%@/%@/%ld", [self class],[tableView class], tableView.tag];
NSDictionary * dic = [[[DataContainer dataInstance].data objectForKey:@"TABLEVIEW"] objectForKey:identifier];
if (dic) {
NSString * eventid = dic[@"userDefined"][@"eventid"];
NSString * targetname = dic[@"userDefined"][@"target"];
NSString * pageid = dic[@"userDefined"][@"pageid"];
NSString * pagename = dic[@"userDefined"][@"pagename"];
NSDictionary * pagePara = dic[@"pagePara"];
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
__block NSMutableDictionary * uploadDic = [NSMutableDictionary dictionaryWithCapacity:0];
[pagePara enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSInteger containIn = [obj[@"containIn"] integerValue];
id instance = containIn == 0 ? self : cell;
id value = [CaptureTool captureVarforInstance:instance withPara:obj];
if (value && key) {
[uploadDic setObject:value forKey:key];
}
}];
NSLog(@"\n event id === %@,\n target === %@, \n pageid === %@,\n pagename === %@,\n pagepara === %@ \n", eventid, targetname, pageid, pagename, uploadDic);
}
}
现在重点来了,我们来说下对于埋点数据是如何处理。
就比如我们对一个tableivew进行了点击。
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
TestModel * model = self.dataArray[indexPath.row];
cell.model = model;
return cell;
}
@interface TestModel : NSObject
@property(nonatomic,assign)NSInteger age;
@property(nonatomic,strong)NSString * name;
@property(nonatomic,strong)NSString * sex;
@property(nonatomic,strong)NSString * genDer;
@property(nonatomic,strong)LikeModel * secondModel;
@end
@interface LikeModel : NSObject
@property(nonatomic,strong)NSString * fly;
@property(nonatomic,strong)NSString * goods;
@property(nonatomic,strong)ThirdModel * model3;
@end
@interface ThirdModel : NSObject
@property(nonatomic,assign)NSInteger grade;
@property(nonatomic,strong)NSString * sex1;
@end
通过上面的埋点结构,我们想要拿到的是,cell ->model->secondModel -> model3->grade 层级结构是这样,那么我们怎么无痕拿到呢?
+(id)captureVarforInstance:(id)instance withPara:(NSDictionary *)para
{
NSString * properyName = para[@"propertyName"];
NSString * propertyPath = para[@"propertyPath"];
if (propertyPath.length > 0) {
NSArray * keysArray = [propertyPath componentsSeparatedByString:@"/"];
return [[self class] captureVarforInstance:instance withKeys:keysArray];
}
return [[self class] captureVarforInstance:instance varName:properyName];
}
第一遍,我们去cell中取找secondModel
image.png
+(id)captureVarforInstance:(id)instance varName:(NSString *)varName
{
id value = [instance valueForKey:varName];
unsigned int count;
objc_property_t *properties = class_copyPropertyList([instance class], &count);
if (!value) {
NSMutableArray * varNameArray = [NSMutableArray arrayWithCapacity:0];
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString* propertyAttributes = [NSString stringWithUTF8String:property_getAttributes(property)];
NSArray* splitPropertyAttributes = [propertyAttributes componentsSeparatedByString:@"\""];
if (splitPropertyAttributes.count < 2) {
continue;
}
NSString * className = [splitPropertyAttributes objectAtIndex:1];
Class cls = NSClassFromString(className);
NSBundle *bundle2 = [NSBundle bundleForClass:cls];
//找到自定义的类。
if (bundle2 == [NSBundle mainBundle]) {
// NSLog(@"自定义的类----- %@", className);
const char * name = property_getName(property);
NSString * varname = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding];
[varNameArray addObject:varname];
} else {
// NSLog(@"系统的类");
}
}
for (NSString * name in varNameArray) {
id newValue = [instance valueForKey:name];
if (newValue) {
value = [newValue valueForKey:varName];
if (value) {
return value;
}else{
value = [[self class] captureVarforInstance:newValue varName:varName];
}
}
}
}
return value;
}
通过上面的方法进行递归进行拿取,一直找到grade为止,然后返回值。
image.png
image.png
image.png
image.png
特别注意:通过这种方式找到自定义的属性,值得学习。
NSBundle *bundle2 = [NSBundle bundleForClass:cls];
//找到自定义的类。
if (bundle2 == [NSBundle mainBundle])
对于UICollectionView的方法跟tableView可以说是完全一样。
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalAppearSelector = @selector(setDelegate:);
SEL swizzingAppearSelector = @selector(user_setDelegate:);
[MethodSwizzingTool swizzingForClass:[self class] originalSel:originalAppearSelector swizzingSel:swizzingAppearSelector];
});
}
-(void)user_setDelegate:(id<UICollectionViewDelegate>)delegate
{
[self user_setDelegate:delegate];
SEL sel = @selector(collectionView:didSelectItemAtIndexPath:);
SEL sel_ = NSSelectorFromString(@"userDefined_collectionView_didselected");
class_addMethod([delegate class],
sel_,
method_getImplementation(class_getInstanceMethod([self class], @selector(user_collectionView:didSelectItemAtIndexPath:))),
nil);
//判断是否有实现,没有的话添加一个实现
if (![self isContainSel:sel inClass:[delegate class]]) {
IMP imp = method_getImplementation(class_getInstanceMethod([delegate class], sel));
class_addMethod([delegate class], sel, imp, nil);
}
// 将swizzle delegate method 和 origin delegate method 交换
[MethodSwizzingTool swizzingForClass:[delegate class] originalSel:sel swizzingSel:sel_];
}
//判断页面是否实现了某个sel
- (BOOL)isContainSel:(SEL)sel inClass:(Class)class {
unsigned int count;
Method *methodList = class_copyMethodList(class,&count);
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *tempMethodString = [NSString stringWithUTF8String:sel_getName(method_getName(method))];
if ([tempMethodString isEqualToString:NSStringFromSelector(sel)]) {
return YES;
}
}
return NO;
}
- (void)user_collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
SEL sel = NSSelectorFromString(@"userDefined_collectionView_didselected");
if ([self respondsToSelector:sel]) {
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL,id,id) = (void *)imp;
func(self, sel,collectionView,indexPath);
}
}
这一篇说完,应该学到的地方,怎么找到并交换tableView collectionView相关的代理方法。
怎么找到具体到哪一个tableView的哪一个cell的点击,怎么找到cell中model下嵌套多层的点击。
其实我觉得无痕埋点,目前的方案远远不是终点,后面还会通过学习更新出新的方案。












网友评论