前言
埋點(diǎn)統(tǒng)計在項目中還是比較常見的,可以用來分析用戶的習(xí)慣熏兄,從而有針對性的去優(yōu)化app秘狞。傳統(tǒng)的做法就是在每個具體的事件觸發(fā)的地方進(jìn)行埋點(diǎn),這種方法比較機(jī)械肯夏,更多的是一項體力活经宏,而且等項目越來越大犀暑,埋的點(diǎn)遍布于真?zhèn)€項目中,可能你自己都找不到烁兰,非常不利于后期的維護(hù)耐亏。當(dāng)然最好的辦法就是利用OC的runtime黑魔法,swift也是可以用的缚柏,只不過有些方法是不可用的苹熏,但是功能是可以滿足的,接下來就帶大家看一下統(tǒng)計進(jìn)行埋點(diǎn)的思路分析币喧。
runtime和Method Swizzling
用到的方法大致是:
public func class_getInstanceMethod(_ cls: Swift.AnyClass!, _ name: Selector!) -> Method!
public func class_addMethod(_ cls: Swift.AnyClass!, _ name: Selector!, _ imp: IMP!, _ types: UnsafePointer!) -> Bool
public func method_exchangeImplementations(_ m1: Method!, _ m2: Method!)
大致的思路就是:找到一個底層的方法(就是事件下發(fā)都會經(jīng)過的一個方法)轨域,然后利用runtime替換調(diào)兩個方法的實(shí)現(xiàn)。
唯一字符串——identifier生成的規(guī)則
為什么要生成identifier杀餐?這是為了保證確定某個控件觸發(fā)的事件是唯一的干发,事件唯一就能夠映射上埋點(diǎn)的事件。那么它的生成規(guī)則是什么史翘?
基本上生成規(guī)則是:當(dāng)前某個視圖名+某控件+action名稱+target名稱枉长。
注意swift是具有命名控件規(guī)則的,獲取的class名一般是這樣的:projectname.classname這種格式的琼讽,用的時候稍微注意下必峰。
具體的埋點(diǎn)實(shí)例
首先定義一個埋點(diǎn)的管理對象:
struct AspectManager {
static func swizzle(inClass `class`: AnyClass, swizzle : Selector, original: Selector){
let originalMethod = class_getInstanceMethod(`class`, original)
let swizzleMethod = class_getInstanceMethod(`class`, swizzle)
let didAddMethod = class_addMethod(`class`, original, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
if didAddMethod {
class_replaceMethod(`class`, swizzle, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzleMethod)
}
}
}
用來交換某個類的兩個方法的實(shí)現(xiàn)。
UIControl
這里我們需要用到的方法是:
open func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?)
只要是繼承于UIControl的事件都會經(jīng)過這個方法钻蹬,接下來直接上代碼:
extension UIControl{
open override class func initialize() {
super.initialize()
//make sure this isn't a subclass
if self !== UIControl.self {
return
}
DispatchQueue.once(token: "com.moglo.niqq.UIControl") {
swizzle()
}
}
fileprivate class func swizzle(){
let originalSelector = #selector(UIControl.sendAction(_:to:for:))
let swizzleSelector = #selector(UIControl.nsh_sendAction(_:to:for:))
AspectManager.swizzle(inClass: self, swizzle: swizzleSelector, original: originalSelector)
}
func nsh_sendAction(_ action: Selector, to target: Any?, for event: UIEvent?){
//因?yàn)榻粨Q了兩個方法的實(shí)現(xiàn)所以不用擔(dān)心會死循環(huán)
nsh_sendAction(action, to: target, for: event)
//在這里做你想要處理的埋點(diǎn)事件吼蚁,identifier可以自己配置一個文件,然后按照生成的id去取定義好的一些需要埋點(diǎn)的事件名
}
}
這樣一來所有的UIButton问欠、UITextView等的事件都會被攔截肝匆,需要處理的事情可以統(tǒng)一在這里處理
UITableView與UICollectionView
這里我們需要替換的是代理方法的cell的點(diǎn)擊事件:
optional public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
那么首先我們需要替換的是代理的設(shè)置:
fileprivate class func swizzle(){
let originalSelector = #selector(setter: UITableView.delegate)
let swizzleSelector = #selector(UITableView.nsh_set(delegate:))
AspectManager.swizzle(inClass: self, swizzle: swizzleSelector, original: originalSelector)
}
func nsh_set(delegate: UITableViewDelegate?){
nsh_set(delegate: delegate)
guard let delegate = delegate else {return}
Logger.Debug(info: "UITableView set delegate:\(delegate)")
}
其次是要給代理添加一個在自定義的方法
func nsh_tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
nsh_tableView(tableView, didSelectRowAt: indexPath)
//這里添加你需要的埋點(diǎn)代碼
}
接下來就是直接替換兩個類的實(shí)現(xiàn),完整代碼:
func nsh_set(delegate: UITableViewDelegate?){
nsh_set(delegate: delegate)
guard let delegate = delegate else {return}
Logger.Debug(info: "UITableView set delegate:\(delegate)")
//交換cell點(diǎn)擊事件
let originalSelector = #selector(delegate.tableView(_:didSelectRowAt:))
let swizzleSelector = #selector(UITableView.nsh_tableView(_:didSelectRowAt:))
let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleSelector)
let didAddMethod = class_addMethod(type(of: delegate), swizzleSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod))
if didAddMethod{
let didSelectOriginalMethod = class_getInstanceMethod(type(of: delegate), NSSelectorFromString("nsh_tableView:didSelectRowAt:"))
let didSelectSwizzledMethod = class_getInstanceMethod(type(of: delegate), originalSelector)
method_exchangeImplementations(didSelectOriginalMethod, didSelectSwizzledMethod)
}
}
UICollectionView同理顺献,這里就不在贅述旗国。
這里總結(jié)的可能還有不完善的地方,希望大家批評指正