原文
本篇文章是基於 網易樂得無埋點數據SDK 總結而成。負責無埋點數據收集 SDK 的開發已經有半年多了,期間在組內進行過相關分享,現在覺得是時候拿出去和同行們交流下了。本篇主要講一下SDK的整體實現思路以及關鍵的技術點。
SDK 已經具備不需要代碼埋點就能 自動的、動態可配的、全面且正確 的收集用戶在使用 App 時的所有事件數據。除此之外,還單獨開發了與之配合的圈選SDK,能夠在 App 端完成對界面元素的圈配以及 KVC 配置的上傳。而界面元素圈配的工作完全可以交給用研與產品人員來做,減輕了開發人員的工作量。
SDK 已有的功能可以分為兩大部分:
基本事件數據的收集:基本事件的收集是指應用冷啟動事件、頁面事件、用戶點擊事件、ScrollView滑動事件等,這部分全部都是自動完成的,實現思路會在第一節中介紹。
業務層數據的收集:業務層數據的收集是指對與業務功能相關的一些數據,例如:在用戶點擊提交訂單按鈕時,收集用戶購買的物品以及訂單總金額的數據。這種業務層數據的收集以往大多通過 代碼埋點 的方式去做,本SDK則真正的實現了 無埋點 的去獲取這些想要的業務數據。這部分的實現會在本文的第二節詳細介紹。
SDK的整體實現思路
SDK 整體采用了 AOP(Aspect-Oriented-Programming)即面向切面編程的思想,就是動態的在函數調用的前後插入數據收集的代碼。在 Objective-C 中的實現是基於 Runtime 特性的 Method Swizzling 黑魔法。
SDK 的數據收集功能的實現主要通過 Method Swizzling 來 hook 相應的方法。hook的方法大致可以分為3類:系統類的方法、系統類的Delegate方法、自定義類的方法。
系統類的方法
系統類的方法是指系統框架中提供的基礎類的方法,如 UIApplication、UIViewController 等。SDK 在實現某些功能時,需要hook這些類的方法。例如在實現對頁面事件的收集時,主要hook了 UIViewController 的生命周期的方法:viewDidLoad、viewDidAppear、viewDidDisappear、dealloc
系統類的 Delegate 方法
系統類的 Delegate 方法主要指 UIKit 框架中提供的 Delegate 中的方法,如 UIScrollViewDelegate、UITableViewDelegate、UIWebViewDelegate 等。SDK 中的大多數功能都是通過hook這些協議中的方法來完成的。例如在實現列表元素點擊事件的收集時,主要 hook 了 UITableViewDelegate 中的 tableView:didSelectRowAtIndexPath: 方法。
自定義類的方法
顧名思義,自定義類的方法是指開發人員在工程中自已定義的類,而非系統類的方法。SDK的一些功能是通過hook 這些類的方法來實現。例如在SDK實現對手勢操作的事件收集時,需要hook手勢對象所指定的target 中的 action 方法,而 target 通常都是自定義類。其實hook系統類的 delegate 方法也可以看成是 hook 自定義類的方法,因為系統類的 delegate 方法大多都是需要在自定義類中實現。
這部分看起來是借助於 AOP 來添加數據收集的代碼,但是在真正做的時候,也並沒有想的那麼簡單,涉及到很多細節上的問題,例如:如何將導航欄與系統彈窗的點擊事件歸屬到合適頁面中、如何區分UIControlEventValueChanged事件、如何解決hook手勢操作引起的性能問題等等。不過這部分內容並不是本篇文章的重點,因此這裡不打算多說,之後會單獨寫一篇文章來講述遇到的一些坑。
SDK的關鍵技術的實現
viewPath 及 viewId 的生成及優化
為了對 APP 中某個頁面的某個 view 進行數據收集、統計與分析,首先就需要能夠唯一的標識與定位這個視圖,這可以說是數據收集 SDK 的一個重要前提。那麼怎樣去唯一的標識 APP 中的某個 view 呢?SDK 中使用了 viewPath 與 viewId 來完成。
1. viewPath 的組成
其實整個 APP 的視圖結構可以看成是一顆樹(viewTree),樹的根節點就是 UIWindow,樹的枝干由UIViewController及UIView組成,樹的葉節點都是由UIView組成。
那麼在viewTree中用什麼信息來表示其中任意一個 view 的位置呢?很容易想到的就是使用目標 view 到根之間的每個節點的深度(層次)組成一個路徑,而節點的深度(層次)是指此節點在父節點中的 index。這樣確實能夠唯一的表示此 view 了,但是有一個缺點:它的可讀性很差。因此在此基礎上又增加了每個節點的名稱,節點的名稱由當前節點的 view 的類名來表示。
因此,在 viewTree 中,由一個 view 到根節點之間的每個節點的名稱與深度(層次)共同組成的信息構成了此 view 的viewPath。另外,由於在做 view 的統計分析時,都是以頁面為單位的,因此 SDK 在生成 viewPath 時,只到 view 所在的 UIViewController 級別,而非根部的 UIWindow。這樣做也在一定程度上減少了viewPath 的長度。
2. UITableViewCell/UICollectionCell 的深度表示
在 App 開發中,最常用而且最重要的控件就是UITableView與UICollectionView。針對這種可復用視圖,裡面會包含很多 Cell,而且 Cell 個數也不確定,那麼裡面的每一個 Cell 應該怎麼去表示其深度呢?答案是indexPath。雖然每個 Cell 都可能被復用,但是不同的 Cell 都對應一個唯一的indexPath,因此完全可以使用indexPath值來表示其深度。
3. viewPath 的表示形式與示例
我們已經知道,viewPath就是由各節點的類名與深度組成,那麼接下來就使用這些信息來表示出 viewPath。下面結合一個具體的示例來簡單說一下,我隨便從項目中找了一個:
路徑中各個節點的類名是:
HYGHallSlideViewController-UIScrollView-HYGHallProductTableView-UITableViewWrapperView-HYGHallProductCell-UITableViewCellContentView-HYGHallProductView。
路徑中各個節點的深度是:0-0-0-0-0:2-0-1
接下來就是將這兩者放到一起來構成 viewPath,SDK 的表示方式如下:
viewPath:HYGHallSlideViewController-UIScrollView-HYGHallProductTableView-UITableViewWrapperView-HYGHallProductCell-UITableViewCellContentView-HYGHallProductView & 0-0-1-0-0:2-0-1
其實就是使用 & 連接符簡單的拼接到一起。這樣做可以方便將兩者組合與分離開,便於後面的viewPath匹配。另外,網上還有一種類似於 xPath 的表示方式:
HYGHallSlideViewController[0]/UIScrollView[0]/HYGHallProductTableView[1]/UITableViewWrapperView[0]/HYGHallProductCell[0:2]/UITableViewCellContentView[0]/HYGHallProductView[1]
不過個人覺得xPath的方式稍微復雜了點,在組合以及拆分上都相對麻煩些。不過話說回來,viewPath的形式是次要的,大家可以按照各自喜歡的方式去表示就行,無須糾結於哪種形式更好。
4.針對 viewPath 的優化
4.1 優化節點的深度的計算方式
上面提到在計算各節點的深度時,是采用當前 view 位於其父 view 中的所有子 view 中的 index 值。不過在實際的開發中,viewTree 有時候會根據用戶的操作有所變動。仍然舉個栗子:
假設一個 UIView 中有三個子 view,先後加入的順序是:label、button1、button2,按照之前的計算方式,這 3 個子 view 的深度依次是:0、1、2。這時候用戶點擊了一個按鈕,label1 從父 view 中被移除了。此時 UIView 只有 2 個子view:button1、button2,而且深度變為了:0、1。如圖所示:
可以看出僅僅由於其中一個子view 被移除,卻導致其它子 view 的深度都發生了變化。因此,SDK 為了在新增/移除某一 view 時,盡量減少對已有 view 的深度的影響,調整了對節點的深度的計算方式:采用當前 view 位於其父 view 中的所有 同類型 子 view 中的index 值。
我們再看一下上面的這個例子,最初 label、button1、button2 的深度依次是:0、0、1。在 label 被移除後,button1、button2 的深度依次為:0、1。可以看出,在這個例子中,label 的移除並未對 button1、button2 的深度造成影響,這種調整後的計算方式在一定程度上增強了 viewPath 的抗干擾性。
另外,調整後的深度的計算方式是依賴於各節點的類型的,因此,此時必須要將各節點的名稱放到viewPath中,而不再是僅僅為了增加可讀性。
4.2 viewPath 針對 Swift 的優化
眾所周知,Swift文件在獲取其類名時,會自動添加此文件所在的Module名前綴:如果Swift文件在主工程中,則會添加工程的名字;如果是在某個組件中,並且項目開啟了 use frameworks! 選項,則會添加組件的名字。總的來說,在含有swift 的項目中(包括純 swift/OC 與 swift 混編),viewPath中會包含各 Swift 文件的ModuleName,那麼在如下情況下:
某個 OC 文件被使用 Swift 重寫了
某個 Swift 文件被從主工程移至某個組件庫中,或者從組件庫移至主工程中
主工程在引用組件庫時,在開啟與關閉use frameworks!之間進行切換
上述3種情況下,文件的類名都會由於ModuleName而發生變化,進而會導致 viewPath 的改變,工程文件在結構上的調整都可能會直接對viewPath造成影響。
實際開發中,特別是對於較老的OC項目,經常會對項目的OC文件使用Swift重寫。因此 SDK 有必要去避免viewPath因為這類情況而發生變化。
其實這個問題的解決方案很簡單,既然是由於類名中的ModuleName前綴的改變造成的,那麼就干脆在生成viewPath時,去掉所有的Swift的ModuleName前綴。這種做法能夠解決對viewPath的影響,但是細心的人可能會意識到另一個隱藏的問題:如果在不同的組件庫中,兩個不同的視圖或控制器具有相同的名字(在Swift中是允許的,因為有Module進行區分),這種情況下,viewPath是否存在無法區分的情況?
其實經過仔細考慮,這個擔憂有點多余,因為就算兩個Module中的視圖或控制器名字一樣,但是他們裡面的視圖結構會有所不同,進而深度也不一樣,viewPath也不會完全相同。
4.3 在包含子VC時,優化VC的深度的計算
前面提到,viewPath只表示到距離 view 最近的一個 VC,VC 的深度的計算也是此 VC 的 view 所在的父 view 的所有子 view 中的深度。在實際的 iOS 開發中,可能會經常使用addChildViewController:添加多個子 VC 來實現復雜的頁面,但是在包含子 VC 時,VC 的深度計算就有可能會存在問題。還是舉一個簡單的栗子:
假設一個 containerVC 中包含4個子VC:VC1、VC2、VC3、VC4。在每個子VC首次被展示時,子VC會先被add進來,而子 VC 的 view 也會被 add 到一個scrollView 上。這時候這幾個子VC首次的查看順序的不同將會導致它們的深度的變化:如果查看順序是:VC1、VC2、VC3、VC4,那麼它們的深度依次為:VC1(0)、VC2(1)、VC3(2)、VC4(3);如果查看順序是:VC3、VC1、VC4、VC2,深度則變成了:VC1(1)、VC2(3)、VC3(0)、VC4(2)。這種情況導致 viewPath 不可靠且無法保證唯一性。
SDK 為了解決上述情況,調整了 VC 的深度的計算:不再采用其 view 的深度,而是直接使用固定的0。因為 VC 已經是viewPath的根級別了,它的深度信息已經不重要了。
不過這種方案會引起另一個小問題,如果上述子 VC 的 VC1 和 VC2 是同一個類的不同實例,那麼他們內部的視圖結構是完全一樣的,這時候如果使用固定的 VC 深度(0),通過viewPath就無法區分具體是哪個子 VC 的 view 了。針對這種同一類的不同實例,如果想進一步區分它們,SDK 采用了另一個方案:頁面別名。
5. viewId 的生成
viewPath 已經能夠唯一標識某個 view 了,為何還需要viewId呢?其實主要原因是:viewPath 的長度不固定,而且一般都會比較長,不便於後台使用它作為 view 的唯一標識。因此 SDK 使用viewPath信息通過MD5加密生成一個固定長度的值作為viewId。
6. viewPath 與 viewId 重復時的解決方案
經過對viewPath的優化,SDK 已經盡可能的保證了viewPath的穩定性。但是並不表示只依靠viewPath就能區分所有的點擊事件。有時同一個viewPath的 view 具有不同的表現形式與作用,例如下面的情況:
同一個按鈕在不同的狀態下,顯示不同的文字。例如:一個按鈕在未添加商品前顯示“添加”;添加了商品之後,立刻顯示成“清除”
同一個view上具有多處點擊事件,例如 SegmentControl、UISwitch、UIStepper等
上面的這2種情況,都是同一個viewPath對應多個事件,此時如果只使用viewPath無法區分出不同的狀態或事件。
針對這類問題,SDK 的解決方案是:viewPath + “其它信息” 。這裡的 “其它信息” 是視不同情況而定的,比如: 在上面的情況1中,“其它信息” 就是按鈕的 title。在情況2中,“其它信息” 是 SegmentControl 的 selectedIndex 和 UISwitch 的 isOn 屬性的值。SDK 在進行數據收集時,會上傳 view 的這些信息,再結合圈選SDK就能讓後台在做統計時區分出這些不同的事件了。
關於“其它信息”,再補充一點,除了 SDK 事先知道要獲取的信息之外,還有一類就是業務數據。例如:有一個商品列表頁,每一行顯示一個商品,如果後台想統計的不是列表中每一行的點擊,而是每個商品的點擊,那麼此時的“其它信息”就應該是productId 了。關於 SDK 對業務層數據的獲取與上報請看下面的介紹。
SDK無埋點業務數據收集的實現
講完了 viewPath 之後,接下來詳細介紹下 SDK 的另一個關鍵技術:基於 viewPath 與 KVC 實現 SDK 的無埋點業務數據收集功能。首先,先簡單分析一下傳統的 代碼埋點 存在的缺點,大致有以下幾個:
埋點代碼與業務邏輯代碼混合在一起,增加了代碼的維護成本;
埋點代碼需要跟隨APP版本一起發布,耽誤數據的收集與統計;
埋點時存在錯埋、漏埋等情況,無法動態更新及添加;
為了解決上述的 代碼埋點 的缺陷,SDK 實現了真正意義上的 無埋點 來對業務數據進行收集。
1. 無埋點的實現架構
SDK 的無埋點功能的實現主要依賴於 viewPath 與 KVC。viewPath前面已經介紹了,它主要用於標識viewTree中的某個 view。而KVC對於 iOS 開發者也不陌生,堪稱 iOS 開發中的黑魔法之一。通過KVC我們能夠通過 key 或 keyPath 直接訪問對象的屬性,而不需要調用明確的存取方法。關於KVC如果不太了解,請自行學習,這裡不再過多闡述。
那麼如何實現不需要代碼埋點就能隨意獲取想要的業務數據呢?先看一下 SDK 的無埋點技術的整體架構圖:
從上圖可以看出,在實現 SDK 的無埋點數據收集時,主要分為3步:上傳KVC配置、請求KVC配置、業務數據的收集與上報。
2. 什麼是 KVC 配置
在上圖中出現了 KVC配置,那麼下面先簡單介紹下什麼是KVC配置。其實 KVC配置 就是一些用來描述 App 應該在什麼時機去收集什麼數據的信息,包含的主要信息有:
appKey:用來標識是哪個應用
appVersion:用來標識應用的版本號
viewEvent:標識某個事件類型(收集時機),例如:ButtonClick、ListItemClick、ViewTap等
viewPath:目標 view 在viewTree中的信息
keyPath:目標 view 與要收集的業務數據間的關聯路徑,用於KVC取值
keyName:為要收集的業務數據定義一個key,最終組成 key-value 的形式上報。用於區分多個收集的數據
3. KVC配置的上傳與下發
上傳KVC配置
利用 圈選SDK 上傳 KVC配置 的操作對於用戶是透明的,主要由開發人員進行上傳與管理。此操作可以在任何時候進行,在想要收集某個或某些版本的 App 中的業務數據時,上傳相應的KVC配置信息至後台即可,達到了根據需要動態可配的效果。
請求KVC配置
SDK 在初始化時會觸發 KVC配置 的請求操作,從後台拉取 App 當前版本對應的所有KVC配置,並將請求結果緩存起來,以提供給下一步使用。
4. 業務數據的收集與上報
這一部分是 SDK 無埋點技術的核心,接下來詳細介紹這部分的實現邏輯。它的實現流程如下:
這個環節的核心是基於viewPath的 view 匹配,主要實現是通過循環遍歷viewPath的每個節點的信息與當前 view 及其父view 依次進行匹配。因此這一步會產生一定的時間與性能消耗。為了盡可能減少這部分的操作,SDK 中使用了一些方式進行優化,其中一個就是基於緩存view的優化。
4.1 基於緩存view的優化
SDK 采用緩存上一次匹配成功的 view 信息的方式,來減少一些不必要的viewPath匹配操作。這裡主要緩存的 view 信息有:
targetView:上一次通過viewPath匹配成功的 view 對象。
indexPath: 上一次通過viewPath匹配成功的 view 的indexPath,如果沒有則為nil。
1)viewEvent 匹配
第一步先進行事件類型的匹配。如果KVC配置信息指定的 viewEvent 是 ButtonClick,那麼可以輕松的過濾掉 ListItemClick、ViewTap 等其它事件。這一步能夠過濾一大部分事件,只有事件類型匹配成功才繼續進行下一步。
2)targetView 匹配
接下來就是將緩存的 targetView 與當前 view 進行比較。如果兩者指向同一對象,則進行第3步,否則直接進入第4步
3)indexPath 匹配
有人可能不明白為何要添加這一步呢?其實這一步也很重要,是對第2步的補充,主要是用來處理 Cell 可復用性的情況。
如果第2步中緩存的 targetView 是 Cell 或 Cell 中的某個 subview,那麼第2步的匹配成功,並不能保證當前 view 就是我們真正想匹配的 view。這個可能不太容易理解,還是舉個簡單的例子來說明一下:
假如一個 Cell 中有一個 button,在第1行的 button 被點擊時,通過viewPath匹配成功了,那麼這時 targetView 緩存了第1行的 button 對象。接下來向下滑動列表,第一行被劃出屏幕,第10行劃入屏幕,同時第10行復用了第1行的 Cell,這時再點擊 button 去匹配時,由於 Cell 復用的原因,targetView 與當前 button 肯定指向同一個對象,但是卻不是我們真正想匹配的第1行的 button。可以看出:在有 Cell 復用的情況下,無法確定第2步的結果一定正確。
因此,在第2步的基礎上又增加了indexPath匹配。indexPath的匹配邏輯為:如果緩存的indexPath不為nil並且與當前view的indexPath不相等,則進入第4步;否則表明當前的 view 就是上次剛剛匹配成功的,也就沒必要進行viewPath匹配,可以直接進入第5步。
4)viewPath 匹配
這一步就是對當前的 view 及其父view 與KVC配置中的viewPath的各個節點進行逐個匹配。由於是一個循環操作,因此會有一定的時間消耗,其實在這部分的匹配中,也做了一些簡單的優化。在真正進入循環匹配之前,先進行如下3步判斷:
判斷 view 類名是否相等;
判斷 view 所在的 viewController 類名是否相等;
判斷 view 所在的 window 類名是否相等;
上述的3個判斷也能過濾很多不必要的匹配。只有這3個判斷均通過後,才進行viewPath循環匹配。
5)KVC 取值與上報
到了這一步,就已經驗證了數據收集的時機是正確的。接下來就可以直接使用 KVC配置信息中的keyPath調用 valueForKeyPath: 方法獲取對應的值。如果值不為nil,就與 keyName 組成一個鍵值對,放到當前的事件數據中一起上報上去。這樣後台就可以通過key去查找到相應的業務數據了。
上面只是簡要介紹了一下匹配時的邏輯,在實際開發中還會添加對 cell 的indexPath通配的情況的處理,由於文章篇幅這裡不再詳細講解。
5. 增加對 KVC 的異常處理
SDK 的無埋點功能的實現其實主要依賴於KVC,但是眾所周知,KVC是非常危險的,很容易造成程序崩潰。例如一旦 key 或 keyPath 所對應的屬性名不存在,立刻會導致程序拋出一個NSUndefinedKeyException異常,如果應用沒有處理此異常,程序就會Crash。
因此,為了避免程序Crash,SDK 內部增加了對KVC異常的處理。具體實現是給 NSObject 增加一個 Category ,重寫 valueForUndefinedKey: 方法,並在方法中return nil。
@implementation NSObject (KVCExceptionHandler) - (nullable id)valueForUndefinedKey:(NSString *)key { return nil; } @end
其它關鍵技術
當然,SDK 的實現中還有很多關鍵技術點,比如:SDK 對 RN 頁面的數據收集、頁面別名方案的實現、Method Swizzling與Aspects的兼容等。由於本文的篇幅已經很長了,而且考慮到大家讀文章的耐性都不會太長,所以這裡就先不講解了,後續會再寫文章單獨介紹。
END
文章寫了這麼多,其實主要介紹了 SDK 中的兩個關鍵技術點,希望對你們能有一些參考價值。另外,如果有人對本文的方案有更好的建議,歡迎一起討論學習。
最後,要特別感謝我的同事王佳樂,由於他對文章的排版與校對工作,才使得本文能更好的展示給大家。同時也要感謝組內的所有同事,在我開發遇到困難時,給予了我很多的幫助。
Q & A
關於對本文內容提出的一些問題,將全部記錄在這裡(簡書評論裡的除外),並進行統一解答。
Q1: SDK 都使用KVC配置獲取業務數據,是否會增加維護KVC配置的工作?
A1: 肯定會有對 KVC配置 的維護與管理工作
一般來說,上傳的所有的 KVC配置 需要與 App 的版本相對應,因為 App 版本不同會直接導致keyPath可能不一樣。所以與 KVC配置 相關的工作有如下2個:
針對當前 App 版本上傳相應的 KVC配置,以獲取想要的業務數據
當 App 新版本發布時,需要對之前版本上的 KVC配置 逐一驗證,是否仍然適用於新版本。如果仍然適用,則直接在管理後台上把新的版本號添加到此 KVC配置;如果不再適用,則對新版本再上傳一個新的KVC配置。
從上面可以看出,在 App 版本不斷迭代的過程中,KVC配置 會越來越多,相應的維護與管理工作也相當繁瑣。
為了解決這個痛點,SDK 中增加了一種方案來避免這種重復且繁瑣的工作。具體的方案是:
在上傳 KVC 配置時,指定某個區間的版本,或者不指定具體的版本(即應用到當前所有版本上);
SDK 在使用KVC配置獲取業務數據失敗時,添加相關的錯誤日志,並上報上去。其中錯誤日志裡包含了appKey、appVersion、keyPath等信息,這樣就能在後台清晰的看到哪些 KVC配置 在哪個 App 版本上存在問題;
使用腳本監控與KVC相關的錯誤日志。如果監控到有錯誤日志上報,則發送郵件通知給相關人員;
因此,SDK 采用此方案優化之後,KVC配置 的管理工作就只有1個了:
根據Log信息快速找到對應的 KVC配置,並上傳一個針對新版本的 KVC配置
Q2: 對於 “內容與位置” 可能會隨時間而變動時,如何實現數據收集與統計?
A2: 使用圈選SDK與數據SDK共同完成動態數據的收集與統計
這個問題在實際產品中也比較常見,比如 App 首頁的內容大多是通過後台配置的。
這個問題其實可以轉化或分解成如下的2個情況:
同一位置會顯示不同的內容
同一內容會顯示在不同的位置
注意,這2個並非同一個,它們分別對應於不同的場景,同時數據收集的方案也有所不同。
另外,“位置” 可以是在列表中,也可以是非列表中的,不過這個對整體的方案沒有太大影響,僅僅是在不關心位置時viewPath中的通配符位置不同。
A2.1 同一位置顯示不同的內容
例子:在 App 首頁有一個展示最近活動的位置,先展示活動1的圖片,過一段時間運營人員又配成活動2的圖片。如何統計活動1、活動2各自的點擊量?
針對這種場景,SDK 的解決方案是:“關心位置” + “關心內容”。
“關心位置” 的意思是只使用當前的位置,具體表現是viewPath中不包含任何通配符;“關心內容” 的意思是指定一個想要統計的內容。
整個過程可以分解為如下3個環節:
圈選SDK上傳“關心位置”的KVC配置。KVC配置中指定獲取活動的url的keyPath。
數據SDK在活動發生點擊時,收集當前活動對應的url,並跟隨點擊事件一起上報。
圈選SDK上傳“關心位置” + “關心內容”的圈選配置,關心的內容指定為想要統計的活動的url值。
A2.2 同一內容顯示在不同的位置
例子:App 首頁有4個固定的入口,假設其中一個叫“熱門推薦”,那麼根據後台配置的順序不同,“熱門推薦”可能被顯示在4個位置中的任何1個,即一段時間顯示在第1個,過一段時間可能顯示在第2個位置。這時如何統計出“熱門推薦”的點擊量?
針對這種場景,SDK 的解決方案是:“不關心位置” + “關心內容”。
“不關心位置” 是指viewPath中含有通配符,用於表示viewTree中的多個位置。例如想要匹配列表所有行時,則將viewPath中的indexPath替換為通配符。
這個問題的解決過程也分為如下3步:
圈選SDK上傳“不關心位置”的KVC配置。KVC配置中指定獲取入口的 title 的keyPath。
數據SDK在4個中任何一個入口被點擊時,都去收集入口的 title,並跟隨點擊事件一起上報。
圈選SDK上傳“不關心位置” + “關心內容”的圈選配置,關心的內容指定為“熱門推薦”。
到這裡,數據收集與圈選配置的工作都已經做完了,接下來就是後台的數據統計了。
上述2種情況對後台進行統計沒有區別,都使用一個統計方案,這裡也介紹一下後台大概的統計思路:
拿到第3步中上傳的圈選配置,根據viewPath 與 “關心的內容” 生成一個正則表達式,然後從數據 SDK 上報的原始數據中進行正則匹配,進而統計出相應數據。