iOS app內(nèi)埋點(diǎn)切面的分析思路(基于swift3)

前言

埋點(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)指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容