用户行为你看我就够了(hook)

目录

<a name="前言"></a>前言

用户行为的统计可以帮助我们更好的了解用户的各方面信息。现在比较主流的有两款三方统计库:友盟(国内)和flurry(国外)。但是用户行为收集的代码往往分散在各个类中,难以维护也不雅观,今天交流一种比较好用的用户行为统计方法。本文将全部采用swift来解析。

<a name="准备工作"></a>准备工作

  • 三方库

我们需要两个三方库:友盟和Aspects.
* 友盟用来做最终的统计收集
* Aspects 是本文的关键点,它hook住了我们想要的方法,高度集中。

我们采用cocoapods 来管理三方库

platform :ios, '8.0'
use_frameworks!

target 'UserBehaviorSwift' do
    #hook
    pod 'Aspects', '~> 1.4.1'
    #友盟统计(错误分析,事件统计)
    pod 'UMengAnalytics-NO-IDFA', '~> 4.0.5'
end

  • 桥接(如果是oc项目可以跳过该项)

因这两个库都是oc写的,故我们需要新建一个bribing文件。命名格式最好按照官方建议的 xxx-Bridging-Header.h。记得勾选上targets。如下图

桥接oc头文件.png

桥接文件还需要配置路径


桥接文件路径.png

有些三方库的head search(不是所有的库都需要,今天这两个库中Aspects需要配置)


桥接文件时必要配置的三方库.png

桥接代码:

#ifndef UserBehaviorSwift_Bridging_Header_h
#define UserBehaviorSwift_Bridging_Header_h

// 这个库不在 headSearch里面设置就找不到。单独加上
#import "Aspects.h"

//友盟不设置就能找到,应该是framework的缘故
#import <UMMobClick/MobClick.h>

#import <UMMobClick/MobClickSocialAnalytics.h>

#endif /* UserBehaviorSwift_Bridging_Header_h */
  • 代码准备

我们需要一个RootVC类和两个继承与RootVC 的A 和 B;一个RootButton.整个项目的代码都已经传到我的github,地址在文章的结尾。


需要的类.png

<a name="页面的hook"></a>页面的hook

  • 一般的用户行为收集的写法都是直接在对应的类中写业务。类越多,就写的越多。太过麻烦,难以维护。
    override func viewWillAppear(_ animated: Bool) {
        MobClick.beginLogPageView("falgA")
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        MobClick.endLogPageView("falgA")
    }
  • 我们怎么写?可以用Aspect 巧妙的hook住vc 的生命周期函数。拿viewWillAppear来举例。
          //进入页面
        let viewWillAppearBlock:@convention(block) (AspectInfo)-> Void = { aspectInfo in
            
            //获得实例
            let instance = aspectInfo.instance() as? RootViewController
            guard instance != nil else {
                return
            }
            
            //实例转 格式化 string
            let className = String.init(reflecting: instance)
            let arr = className.components(separatedBy: ".")
            guard arr.count > 1 else {
                return
            }
            
            //最终类名
            let finalClassName = arr[1].components(separatedBy: ":").first! as String
            
            //标题
            let title = instance?.title != nil ? instance?.title : "unknown"
            
            
            //最终使用 : type + 类名 + 标题 ,用“/”来分割
            let logFlag = "pageIn/" + finalClassName + "/" + title!
            MobClick.beginLogPageView(logFlag)
            print(logFlag)
        }
        
        //转换
        let viewWillAppearBlockWrapped: AnyObject = unsafeBitCast(viewWillAppearBlock, to: AnyObject.self)
        
        //最终hook住对应的函数,这里设置了AspectOptions.positionBefore模式,会在viewWillAppear 即将被调用前调用
        do {
            try RootViewController.aspect_hook(#selector(RootViewController.viewWillAppear(_:)), with: AspectOptions.positionBefore, usingBlock: viewWillAppearBlockWrapped)
        }
        catch {
            print(error)
        }

解析

用RootVC hook 住viewWillAppear,所有继承与RootVC的类在每次的viewWillAppear被调用之前都会调用我么已经准备好的block。因是swift 调用oc 故用convention修饰,这里如果直接使用闭包会 crash。block里会返还一个当前调用者的实例,用实例我们可以获得 类名、标题。我们设定了格式来确保这个flag的唯一性质 :进入页面("pageIn/" + finalClassName + "/" + title),离开页面("pageOut/" + finalClassName + "/" + title)。最后我们使用 MobClick.beginLogPageView(logFlag) 对其行为进行收集。

<a name="按钮的hook"></a>按钮的hook

按钮的hook相对于页面来说会复杂一点,多了一些步骤。

          //按钮
        let touchesBeganBlock:@convention(block) (AspectInfo)-> Void = { aspectInfo in
            let instance = aspectInfo.instance() as? RootButton
            
            guard instance != nil else {
                return
            }
            
            let className = String.init(reflecting: instance?.allTargets.first)
            let arr = className.components(separatedBy: ".")
            guard arr.count > 1 else {
                return
            }
            
            let finalClassName = arr[1].components(separatedBy: ":").first! as String
            
            let title = instance?.titleLabel?.text != nil ? instance?.titleLabel?.text : "unknown"

            let logFlag = "event/" + finalClassName + "/" + title!
            
            let path = Bundle.main.path(forResource: "UserBehavior", ofType: "plist")
            let dict = NSDictionary.init(contentsOfFile: path!)
            let id = dict?.object(forKey: logFlag) as? String
            guard id != nil else {
                return
            }
            
            MobClick.event(id, label: logFlag)

            print(logFlag)
        }

解析

相对于类来说,按钮需要通过alltargets 的一系列格式化才能得到类名。最终格式为("event/" + finalClassName + "/" + title)。因MobClick.event 事件一般都需要产品经理 给你一套他们定制的id,假如你反驳不了的话,那么就好好的享受吧!这里用了plist进行管理,把所有的按钮和对应的id都写进去,用的时候再取出来。

注意: 我么在解析的时候可能碰到一些没有标题或者其他的情况,所有要严格的进行校验,宁愿少记录一条都碍事,也要避免crash.

plist如下图所示

plist.png

效果图

最终效果.gif

<a name="总结"></a>总结

实际开发中可能不仅仅需要到 页面和按钮这两种,但都是一样的道理,大多都可以通过这种方法来写,个别写不了的就直接用原始方法写也是无伤大碍。所有的代码都已经传到Github

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

推荐阅读更多精彩内容