在開發過程當中,埋點能夠解決兩大類問題:一是瞭解用戶使用 App 的行爲,二是下降分析線上問題的難度。目前,iOS 開發中常見的埋點方式,主要包括:面試
代碼埋點架構
代碼埋點主要就是經過手寫代碼的方式來埋點,能很精確的在須要埋點的代碼處加上埋點的代碼,能夠很方便地記錄當前環境的變量值,方便調試,並跟蹤埋點內容,但存在開發工做量大,而且埋點代碼處處都是,後期難以維護等問題。框架
缺點:學習
優勢:spa
可視化埋點3d
就是將埋點增長和修改的工做可視化了,提高了增長和維護埋點的體驗。調試
該方案的具體步驟就是:blog
用UIViewController、UIControl爲例子,講解一下該方案的思路。繼承
UIViewController PV統計,頁面的統計較爲簡單,利用Method Swizzing hook 系統的viewDidLoad, 直接經過頁面名稱便可鎖定頁面的展現代碼以下:索引
UIControl 點擊統計,主要經過hook sendAction:to:forEvent: 來實現, 其惟一標識符咱們用 targetname/selector/tag來標記,具體代碼以下:
缺點:
優勢:
無埋點
無埋點,並非不須要埋點,而更確切地說是「全埋點」,並且埋點代碼不會出如今業務代碼中,容易管理和維護。它的缺點在於,埋點成本高,後期的解析也比較複雜,再加上 view_path 的不肯定性。因此,這種方案並不能解決全部的埋點需求,但對於大量通用的埋點需求來講,可以節省大量的開發和維護成本。
在這其中,可視化埋點和無埋點,都屬因而無侵入的埋點方案,由於它們都不須要在工程代碼中寫入埋點代碼。因此,採用這樣的無侵入埋點方案,既能夠作到埋點被統一維護,又能夠實現和工程代碼的解耦。
接下來,咱們就經過今天這篇文章,一塊兒來分析一下無侵入埋點方案的實現問題吧。
運行時方法替換方式進行埋點
咱們都知道,在 iOS 開發中最多見的三種埋點,就是對頁面進入次數、頁面停留時間、點擊事件的埋點。對於這三種常見狀況,咱們均可以經過運行時方法替換技術來插入埋點代碼,以實現無侵入的埋點方法。具體的實現方法是:先寫一個運行時方法替換的類 ViewHook,加上替換的方法 hookClass:fromSelector:toSelector,代碼以下:
這個方法利用運行時method_exchangeImplementations 接口將方法的實現進行了交換,原方法調用時就會被hook 住,從而去執行指定的方法。
頁面進入次數、頁面停留時間都須要對 UIViewController 生命週期進行埋點,你能夠建立一個 UIViewController 的 Category,代碼以下:
能夠看到,Category 在+load() 方法裏使用了 ViewHook 進行方法替換,在替換的方法裏執行須要埋點的方法 [self insertToViewWillAppear]。 這樣的話,每一個UIViewController生命週期到了ViewWillAppear 時都會去執行insertToViewWillAppear 方法。
那麼,咱們要怎麼區別不一樣的 UIViewController 呢?我通常採起的作法都是,使用NSStringFromClass([self class]) 方法來取類名。這樣,我就可以經過類名來區別不一樣的 UIViewController 了。
做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交
流羣: 519832104 無論你是小白仍是大牛歡迎入駐,分享經驗,討論技術,你們一塊兒交流學習成長!
另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,能夠添加
iOS開發進階交流羣,進羣可自行下載!
對於點擊事件來講,咱們也能夠經過運行時方法替換的方式進行無侵入埋點。這裏最主要的工做是,找到這個點擊事件的方法 sendAction:to:forEvent:,而後在 +load() 方法使用 ViewHook 替換成爲你定義的方法。完整代碼實現以下:
和 UIViewController 生命週期埋點不一樣的是,UIButton 在一個視圖類中可能有多個不一樣的繼承類,相同 UIButton 的子類在不一樣視圖類的埋點也要區別開。因此,咱們須要經過 「action 選擇器名NSStringFromSelector(action)」 +「視圖類名 NSStringFromClass([target class])」組合成一個惟一的標識,來進行埋點記錄。
除了 UIViewController、UIButton 控件之外,Cocoa 框架的其餘控件均可以使用這種方法來進行無侵入埋點。以 Cocoa 框架中最複雜的 UITableView 控件爲例,你可使用 hook setDelegate 方法來實現無侵入埋點。另外,對於 Cocoa 框架中的手勢事件(Gesture Event),咱們也能夠經過 hook initWithTarget:action: 方法來實現無侵入埋點。
事件惟一標識
經過運行時方法替換的方式,咱們可以 hook 住全部的 Objective-C 方法,能夠說是大而全了,可以幫助咱們解決絕大部分的埋點問題。
可是,這種方案的精確度還不夠高,還沒法區分相同類在不一樣視圖樹節點的狀況。好比,一個視圖下相同 UIButton 的不一樣實例,僅僅經過 「action 選擇器名」+「視圖類名」的組合還不可以區分開。這時,咱們就須要有一個惟一標識來區分不一樣的事件。接下來,我就跟你說說如何制定出這個惟一標識。
這時,我首先想到的就是,能不能經過視圖層級的路徑來解決這個問題。由於每一個頁面都有一個視圖樹結構,經過視圖的 superview 和 subviews 的屬性,咱們就可以還原出每一個頁面的視圖樹。視圖樹的頂層是 UIWindow,每一個視圖都在樹的子節點上。以下圖所示:
一個視圖下的子節點多是同一個視圖的不一樣實例,好比上圖中 UIView 視圖節點下的兩個 UIButton 是同一個類的不一樣實例,因此光靠視圖樹的路徑仍是無法惟一肯定出視圖的標識。那麼,這種狀況下,咱們又應該如何區別不一樣的視圖呢?
這時,咱們想到了索引:每一個子視圖在父視圖中都會有本身的索引,因此若是咱們再加上這個索引的話,每一個視圖的標識就是惟一的了。
接下來的一個問題是,視圖層級路徑加上在父視圖中的索引來進行惟一標識,是否是就可以涵蓋全部狀況了呢?
固然不是。咱們還須要考慮相似 UITableViewCell 這種具備可複用機制的視圖,Cell 會在頁面滾動時不斷複用,因此加索引的方式仍是無法用。
但這個問題也並非無解的。UITableViewCell 須要使用 indexPath,這個值裏包含了 section 和 row 的值。因此,咱們能夠經過 indexPath 來肯定每一個 Cell 的惟一性。
除了 UITableViewCell 這種狀況以外, UIAlertController 也比較特殊。它的特殊性在於視圖層級的不固定,由於它可能出如今任何頁面中。可是,咱們都知道它的功能區分每每經過彈窗內容來決定,因此能夠經過內容來肯定它的惟一標識。
除此以外,還有更多須要特殊處理的狀況,但咱們老是能夠經過一些辦法去肯定它們的惟一性,因此我在這裏也就再也不一一列舉了。思路上來講就是,想辦法找出元素間不相同的因素而後進行組合,最後造成一個可以區別於其餘元素的標識來。
除了上面提到的這些特殊狀況外,還有一種狀況使得咱們也難以獲得準確的惟一標識。若是視圖層級在運行時會被更改,好比執行 insertSubView:atIndex:、removeFromSuperView 等方法時,咱們也沒法獲得惟一標識,即便只截取部分路徑也沒法保證後期代碼更新時不會動到這個部分。就算是運行時視圖層級不會修改,之後需求迭代頁面更新頻繁的話,視圖惟一標識也須要同步的更新維護。
這種問題就很差解決了,事件惟一標識的準確性難以保障,這也是經過運行時方法替換進行無侵入埋點很難在各個公司全面鋪開的緣由。雖然無侵入埋點沒法覆蓋到全部狀況,全面鋪開面臨挑戰,可是無侵入埋點仍是解決了大部分的埋點需求,也節省了大量的人力成本。
最好的方案永遠是針對於不一樣的場景來講的,咱們不可能在一個創業團隊一開始就選擇方案3的架構,因此對於你來講,你要本身抉擇目前而言對你最好的方案,若是你沒有後臺業務同窗的支持,方案1也許對你來講真的是最好的方案了,起碼是能夠完成統計需求,雖然苦點累點。可是在合適的時間,切換不一樣的選擇,纔是成長的體現,仍是最開始的話,若是你在的團隊,已經給你了資源和時間去完善埋點這個模塊,若是你把它作的更好,那必定是一件很酷的事情。