iOS 无侵入埋点的实践记录及思考

前言

在初期,没有做好埋点工作,或者着急赶时间,未能合理的做好埋点的工作,随着用户的增多,就会有分析用户的行为需求,统计某个页面用户的留存时间,虽然市面上有很多统计的SDK,他们大部分都是需要一个页面一个页面去添加,这对于程序猿来说是很不友好的,工作量又大,又不好管理,突然有一天需要修改某个地方,又要挨个去查找添加的埋点方法,去重新更改一遍。怎么样才能做好统一管理这些埋点的工作,让他们都统一到一块,又方便管理,是我们需要思考的,而且这样也节省了大家的时间。

思考

大家都知道objective-c是运行时的机制,所谓运行时就是将数据类型的确定由编译期延迟到了运行时,objective-c是通过runtime来实现的,它是一个非常强大的C语言库 ,这个代码很早以前就开源了,想要了解objective-c,可以看看Apple的Github
Apple opensource开源代码。我们平时所编写的objective-c代码,会在运行时转换成runtimec语言代码,objective-c通过runtime创建类跟对象,并进行消息的发送与转发。

在做无侵入埋点的同时,我们需要了解下我们做埋点统计时需要在什么地方进行埋点统计。
以下是我的埋点思路


image.png

实践

我们确定了需要在什么地方进行埋点,接下来就开始实践,Show me your code

首先我们写个工具类用来统计页面

///后期用到交换方法比较多,统一一个函数进行方法交换
- (void)ljl_exchangeMethodWithClass:(Class)cls
                        originalSEL:(SEL)originalSEL
                          changeSEL:(SEL)changeSEL{
    Method originalMethod = class_getInstanceMethod(cls, originalSEL);
    Method changeMethod = class_getInstanceMethod(cls, changeSEL);
    method_exchangeImplementations(originalMethod, changeMethod);
}

记录打印日志统一管理

- (void)recordHookClass:(Class)cls identifier:(NSString *)identifier{
    NSLog(@"当前类名:%@",NSStringFromClass(cls));
    NSLog(@"标识符:%@",identifier);
    
}

UIViewController统计

+(void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            ///获取
            SEL willAppear = @selector(viewWillAppear:);
            SEL hook_willAppear = @selector(hook_viewWillAppear:);
            [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:willAppear changeSEL:hook_willAppear];
            
          
            SEL disappear = @selector(viewDidDisappear:);
            SEL hook_disappear = @selector(hook_viewDidDisappear:);
            [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:disappear changeSEL:hook_disappear];
            
        });
}

方法实现


- (void)hook_viewWillAppear:(BOOL)animated{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"进入"];
    [self hook_viewWillAppear:animated];
}

- (void)hook_viewDidDisappear:(BOOL)animated{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"离开"];
    [self hook_viewDidDisappear:animated];
}

此方案只是针对用户的停留时间及用户的进入次数,日志打印可按需求来统计,不同的需求进行不同的方式。

UITableView

UITableViewUICollectionView统计用户点击cell的方法都是在代理中,我们需要进行替换设置delegate的方法,在、setDelegate:方法中插入统计的代码,这里有个小坑,有的页面是没有实现didSelectRowAtIndexPath,为了使得方法不交换可以判断下是否实现了didSelectRowAtIndexPath再进行统计操作。

Code

+(void)load{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSEL = @selector(setDelegate:);
        SEL changeSEL = @selector(hook_setDelegate:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
        
    });
  
}

函数实现

- (void)hook_setDelegate:(id<UITableViewDelegate>)delegate{
        [self hook_setDelegate:delegate];
        Method didSelectmethod = class_getInstanceMethod(delegate.class, @selector(tableView:didSelectRowAtIndexPath:));
        IMP hookIMP = class_getMethodImplementation(self.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
        
        char const* type = method_getTypeEncoding(didSelectmethod);
        class_addMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:), hookIMP, type);
        Method hookMethod = class_getInstanceMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
        method_exchangeImplementations(didSelectmethod, hookMethod);

}

- (void)hook_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:[NSString stringWithFormat:@"%ld,%ld",indexPath.row,indexPath.section]];
    [self hook_tableView:tableView didSelectRowAtIndexPath:indexPath];
}

UIButton的点击事件

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///获取
        
        SEL originalSEL = @selector(sendAction:to:forEvent:);
        SEL changeSEL = @selector(hook_sendAction:to:forEvent:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
    });
}

///MAKR:
- (void)hook_sendAction:(SEL)action
                     to:(nullable id)target
               forEvent:(nullable UIEvent *)event{
    [self hook_sendAction:action to:target forEvent:event];
    ///点击事件结束记录
    if ([[event.allTouches anyObject]phase] == UITouchPhaseEnded) {
        [[LJL_HookObjcLog logManage] recordLogActionHookClass:[target class] action:action identifier:@"UIButton"];
    }
}

UIGestureRecognizer手势的Hook方法

@implementation UIGestureRecognizer (Log_Category)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///获取
        
        SEL originalSEL = @selector(initWithTarget:action:);
        SEL changeSEL = @selector(hook_initWithTarget:action:);
        [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
    });
}

- (instancetype)hook_initWithTarget:(nullable id)target action:(nullable SEL)action{
    UIGestureRecognizer *gestureRecognizer = [self hook_initWithTarget:target action:action];
    SEL changeSEL = @selector(hook_gestureAction:);
    IMP hookIMP = class_getMethodImplementation(self.class, changeSEL);
    const char *type = method_getTypeEncoding(class_getInstanceMethod([target class], action));
    class_addMethod([target class], changeSEL, hookIMP, type);
    
    [[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:[target class] originalSEL:action changeSEL:changeSEL];
    
    
    return gestureRecognizer;
}

- (void)hook_gestureAction:(id)sender{
    [self hook_gestureAction:sender];
    [[LJL_HookObjcLog logManage] recordLogActionHookClass:[sender class] action:@selector(action) identifier:@"手势"];

}

@end


思考总结

本文简单讲述无侵入埋点的统计方案,思路大致上是通过Runtime的运行机制,在运行期可以向类中新增或替换选择子所对应的方法实现。使用另外一份实现原有的方法实现。
在无侵入的基础上,即降低了代码的耦合,又方便了后期维护管理,相对于可视化埋点,方便简单,所有方式都会有优点与缺点。
本文描述的优点就是无侵入,低耦合,好管理维护。
缺点:有些页面是复用机制,比如cell的复用,一个控制器可能多次进入,需要我们做好统一管理的标识符,一个button的点击需要递归获取当前的控制器等操作。有些模块可能会出现统计不准确等因素,还有可能团队人员多了,定义的方法有时候都是一致的,这样对于这种无侵入的方式最终的效果是不太准确的。相比较可视化埋点,数据统计的更加合理,准确,维护成本略高
每个项目所要统计的内容不一致,精确的程度也不一样,都是各自的观点,本文只是自己的理解与记忆,如果你又什么更好的方案可以留言分享,谢谢。

可参考链接
有货 iOS 数据非侵入式自动采集探索实践
网易HubbleData无埋点SDK在iOS端的设计与实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • 浩瀚而神秘的互加太空中,有这样一颗耀眼的星星:它用声音装点校园,它用旋律吸引孩子,它用多情打动心灵。它就是《快乐音...
    宁都2021杨春美阅读 623评论 1 13
  • 以37岁英年谢世的张荫麟,用半部《中国史纲》在士林赢得盛誉。“张荫麟先生,史学家也,亦哲学家也。其宏博之思,蕴诸中...
    吴玫阅读 580评论 0 1
  • 佟大为和王一博将这些道理都恰到好处的演绎了出来,由俭入奢易,由奢入俭难;每一个职位都不分贵贱,只是看你是否有足够的...
    负重一万斤长大的大孩子阅读 209评论 0 2