前言
埋點(diǎn)統(tǒng)計(jì)在項(xiàng)目中還是比較常見的,可以用來分析用戶的習(xí)慣,從而有針對(duì)性的去優(yōu)化app。傳統(tǒng)的做法就是在每個(gè)具體的事件觸發(fā)的地方進(jìn)行埋點(diǎn),這種方法比較機(jī)械,更多的是一項(xiàng)體力活,而且等項(xiàng)目越來越大,埋的點(diǎn)遍布于真?zhèn)€項(xiàng)目中,可能你自己都找不到,非常不利于后期的維護(hù)。當(dāng)然最好的辦法就是利用OC的runtime黑魔法,swift也是可以用的,只不過有些方法是不可用的,但是功能是可以滿足的,接下來就帶大家看一下統(tǒng)計(jì)進(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!)
大致的思路就是:找到一個(gè)底層的方法(就是事件下發(fā)都會(huì)經(jīng)過的一個(gè)方法),然后利用runtime替換調(diào)兩個(gè)方法的實(shí)現(xiàn)。
唯一字符串——identifier生成的規(guī)則
為什么要生成identifier?這是為了保證確定某個(gè)控件觸發(fā)的事件是唯一的,事件唯一就能夠映射上埋點(diǎn)的事件。那么它的生成規(guī)則是什么?
基本上生成規(guī)則是:當(dāng)前某個(gè)視圖名+某控件+action名稱+target名稱。
注意swift是具有命名控件規(guī)則的,獲取的class名一般是這樣的:projectname.classname這種格式的,用的時(shí)候稍微注意下。
具體的埋點(diǎn)實(shí)例
首先定義一個(gè)埋點(diǎn)的管理對(duì)象:
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)
}
}
}
用來交換某個(gè)類的兩個(gè)方法的實(shí)現(xiàn)。
UIControl
這里我們需要用到的方法是:
open func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?)
只要是繼承于UIControl的事件都會(huì)經(jīng)過這個(gè)方法,接下來直接上代碼:
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了兩個(gè)方法的實(shí)現(xiàn)所以不用擔(dān)心會(huì)死循環(huán)
nsh_sendAction(action, to: target, for: event)
//在這里做你想要處理的埋點(diǎn)事件,identifier可以自己配置一個(gè)文件,然后按照生成的id去取定義好的一些需要埋點(diǎn)的事件名
}
}
這樣一來所有的UIButton、UITextView等的事件都會(huì)被攔截,需要處理的事情可以統(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)")
}
其次是要給代理添加一個(gè)在自定義的方法
func nsh_tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
nsh_tableView(tableView, didSelectRowAt: indexPath)
//這里添加你需要的埋點(diǎn)代碼
}
接下來就是直接替換兩個(gè)類的實(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é)的可能還有不完善的地方,希望大家批評(píng)指正