iOS無埋點數(shù)據SDK實踐之路

本篇文章是基于 網易樂得無埋點數(shù)據SDK 總結而成。負責無埋點數(shù)據收集 SDK 的開發(fā)已經有半年多了,期間在組內進行過相關分享,現(xiàn)在覺得是時候拿出去和同行們交流下了。本篇主要講一下SDK的整體實現(xiàn)思路以及關鍵的技術點。

SDK 已經具備不需要代碼埋點就能 自動的、動態(tài)可配的、全面且正確 的收集用戶在使用 App 時的所有事件數(shù)據。除此之外,還單獨開發(fā)了與之配合的圈選SDK,能夠在 App 端完成對界面元素的圈配以及 KVC 配置的上傳。而界面元素圈配的工作完全可以交給用研與產品人員來做,減輕了開發(fā)人員的工作量。

SDK 已有的功能可以分為兩大部分:

  • 基本事件數(shù)據的收集:基本事件的收集是指應用冷啟動事件、頁面事件、用戶點擊事件、ScrollView滑動事件等,這部分全部都是自動完成的,實現(xiàn)思路會在第一節(jié)中介紹。
  • 業(yè)務層數(shù)據的收集:業(yè)務層數(shù)據的收集是指對與業(yè)務功能相關的一些數(shù)據,例如:在用戶點擊提交訂單按鈕時,收集用戶購買的物品以及訂單總金額的數(shù)據。這種業(yè)務層數(shù)據的收集以往大多通過 代碼埋點 的方式去做,本SDK則真正的實現(xiàn)了 無埋點 的去獲取這些想要的業(yè)務數(shù)據。這部分的實現(xiàn)會在本文的第二節(jié)詳細介紹。

SDK的整體實現(xiàn)思路

SDK 整體采用了 AOP(Aspect-Oriented-Programming)即面向切面編程的思想,就是動態(tài)的在函數(shù)調用的前后插入數(shù)據收集的代碼。在 Objective-C 中的實現(xiàn)是基于 Runtime 特性的 Method Swizzling 黑魔法。

SDK 的數(shù)據收集功能的實現(xiàn)主要通過 Method Swizzlinghook 相應的方法。hook的方法大致可以分為3類:系統(tǒng)類的方法、系統(tǒng)類的Delegate方法、自定義類的方法。

系統(tǒng)類的方法

系統(tǒng)類的方法是指系統(tǒng)框架中提供的基礎類的方法,如 UIApplication、UIViewController 等。SDK 在實現(xiàn)某些功能時,需要hook這些類的方法。例如在實現(xiàn)對頁面事件的收集時,主要hookUIViewController 的生命周期的方法:viewDidLoad、viewDidAppear、viewDidDisappear、dealloc

系統(tǒng)類的 Delegate 方法

系統(tǒng)類的 Delegate 方法主要指 UIKit 框架中提供的 Delegate 中的方法,如 UIScrollViewDelegate、UITableViewDelegate、UIWebViewDelegate 等。SDK 中的大多數(shù)功能都是通過hook這些協(xié)議中的方法來完成的。例如在實現(xiàn)列表元素點擊事件的收集時,主要 hookUITableViewDelegate 中的 tableView:didSelectRowAtIndexPath: 方法。

自定義類的方法

顧名思義,自定義類的方法是指開發(fā)人員在工程中自已定義的類,而非系統(tǒng)類的方法。SDK的一些功能是通過hook 這些類的方法來實現(xiàn)。例如在SDK實現(xiàn)對手勢操作的事件收集時,需要hook手勢對象所指定的target 中的 action 方法,而 target 通常都是自定義類。其實hook系統(tǒng)類的 delegate 方法也可以看成是 hook 自定義類的方法,因為系統(tǒng)類的 delegate 方法大多都是需要在自定義類中實現(xiàn)。

這部分看起來是借助于 AOP 來添加數(shù)據收集的代碼,但是在真正做的時候,也并沒有想的那么簡單,涉及到很多細節(jié)上的問題,例如:如何將導航欄與系統(tǒng)彈窗的點擊事件歸屬到合適頁面中、如何區(qū)分UIControlEventValueChanged事件、如何解決hook手勢操作引起的性能問題等等。不過這部分內容并不是本篇文章的重點,因此這里不打算多說,之后會單獨寫一篇文章來講述遇到的一些坑。

SDK的關鍵技術的實現(xiàn)

viewPath 及 viewId 的生成及優(yōu)化

為了對 APP 中某個頁面的某個 view 進行數(shù)據收集、統(tǒng)計與分析,首先就需要能夠唯一的標識與定位這個視圖,這可以說是數(shù)據收集 SDK 的一個重要前提。那么怎樣去唯一的標識 APP 中的某個 view 呢?SDK 中使用了 viewPathviewId 來完成。

1. viewPath 的組成

其實整個 APP 的視圖結構可以看成是一顆樹(viewTree),樹的根節(jié)點就是 UIWindow,樹的枝干由UIViewControllerUIView組成,樹的葉節(jié)點都是由UIView組成。

那么在viewTree中用什么信息來表示其中任意一個 view 的位置呢?很容易想到的就是使用目標 view 到根之間的每個節(jié)點的深度(層次)組成一個路徑,而節(jié)點的深度(層次)是指此節(jié)點在父節(jié)點中的 index。這樣確實能夠唯一的表示此 view 了,但是有一個缺點:它的可讀性很差。因此在此基礎上又增加了每個節(jié)點的名稱,節(jié)點的名稱由當前節(jié)點的 view 的類名來表示。

因此,在 viewTree 中,由一個 view 到根節(jié)點之間的每個節(jié)點的名稱與深度(層次)共同組成的信息構成了此 view 的viewPath。另外,由于在做 view 的統(tǒng)計分析時,都是以頁面為單位的,因此 SDK 在生成 viewPath 時,只到 view 所在的 UIViewController 級別,而非根部的 UIWindow。這樣做也在一定程度上減少了viewPath 的長度。

2. UITableViewCell/UICollectionCell 的深度表示

在 App 開發(fā)中,最常用而且最重要的控件就是UITableViewUICollectionView。針對這種可復用視圖,里面會包含很多 Cell,而且 Cell 個數(shù)也不確定,那么里面的每一個 Cell 應該怎么去表示其深度呢?答案是indexPath。雖然每個 Cell 都可能被復用,但是不同的 Cell 都對應一個唯一的indexPath,因此完全可以使用indexPath值來表示其深度。

3. viewPath 的表示形式與示例

我們已經知道,viewPath就是由各節(jié)點的類名與深度組成,那么接下來就使用這些信息來表示出 viewPath。下面結合一個具體的示例來簡單說一下,我隨便從項目中找了一個:

路徑中各個節(jié)點的類名是:

HYGHallSlideViewController-UIScrollView-HYGHallProductTableView-UITableViewWrapperView-HYGHallProductCell-UITableViewCellContentView-HYGHallProductView。

路徑中各個節(jié)點的深度是:0-0-1-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 的優(yōu)化

4.1 優(yōu)化節(jié)點的深度的計算方式

上面提到在計算各節(jié)點的深度時,是采用當前 view 位于其父 view 中的所有子 view 中的 index 值。不過在實際的開發(fā)中,viewTree 有時候會根據用戶的操作有所變動。仍然舉個栗子:

  • 假設一個 UIView 中有三個子 view,先后加入的順序是:label、button1、button2,按照之前的計算方式,這 3 個子 view 的深度依次是:0、1、2。這時候用戶點擊了一個按鈕,label1 從父 view 中被移除了。此時 UIView 只有 2 個子view:button1、button2,而且深度變?yōu)榱耍?、1。如圖所示:

可以看出僅僅由于其中一個子view 被移除,卻導致其它子 view 的深度都發(fā)生了變化。因此,SDK 為了在新增/移除某一 view 時,盡量減少對已有 view 的深度的影響,調整了對節(jié)點的深度的計算方式:采用當前 view 位于其父 view 中的所有 同類型 子 view 中的index 值。

我們再看一下上面的這個例子,最初 label、button1、button2 的深度依次是:0、0、1。在 label 被移除后,button1、button2 的深度依次為:0、1??梢钥闯?,在這個例子中,label 的移除并未對 button1、button2 的深度造成影響,這種調整后的計算方式在一定程度上增強了 viewPath 的抗干擾性。

另外,調整后的深度的計算方式是依賴于各節(jié)點的類型的,因此,此時必須要將各節(jié)點的名稱放到viewPath中,而不再是僅僅為了增加可讀性。

4.2 viewPath 針對 Swift 的優(yōu)化

眾所周知,Swift文件在獲取其類名時,會自動添加此文件所在的Module名前綴:如果Swift文件在主工程中,則會添加工程的名字;如果是在某個組件中,并且項目開啟了 use frameworks! 選項,則會添加組件的名字??偟膩碚f,在含有swift 的項目中(包括純 swift/OC 與 swift 混編),viewPath中會包含各 Swift 文件的ModuleName,那么在如下情況下:

  • 某個 OC 文件被使用 Swift 重寫了
  • 某個 Swift 文件被從主工程移至某個組件庫中,或者從組件庫移至主工程中
  • 主工程在引用組件庫時,在開啟與關閉use frameworks!之間進行切換

上述3種情況下,文件的類名都會由于ModuleName而發(fā)生變化,進而會導致 viewPath 的改變,工程文件在結構上的調整都可能會直接對viewPath造成影響。

實際開發(fā)中,特別是對于較老的OC項目,經常會對項目的OC文件使用Swift重寫。因此 SDK 有必要去避免viewPath因為這類情況而發(fā)生變化。

其實這個問題的解決方案很簡單,既然是由于類名中的ModuleName前綴的改變造成的,那么就干脆在生成viewPath時,去掉所有的SwiftModuleName前綴。這種做法能夠解決對viewPath的影響,但是細心的人可能會意識到另一個隱藏的問題:如果在不同的組件庫中,兩個不同的視圖或控制器具有相同的名字(在Swift中是允許的,因為有Module進行區(qū)分),這種情況下,viewPath是否存在無法區(qū)分的情況?

其實經過仔細考慮,這個擔憂有點多余,因為就算兩個Module中的視圖或控制器名字一樣,但是他們里面的視圖結構會有所不同,進而深度也不一樣,viewPath也不會完全相同。

4.3 在包含子VC時,優(yōu)化VC的深度的計算

前面提到,viewPath只表示到距離 view 最近的一個 VC,VC 的深度的計算也是此 VC 的 view 所在的父 view 的所有子 view 中的深度。在實際的 iOS 開發(fā)中,可能會經常使用addChildViewController:添加多個子 VC 來實現(xiàn)復雜的頁面,但是在包含子 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就無法區(qū)分具體是哪個子 VC 的 view 了。針對這種同一類的不同實例,如果想進一步區(qū)分它們,SDK 采用了另一個方案:頁面別名。

5. viewId 的生成

viewPath 已經能夠唯一標識某個 view 了,為何還需要viewId呢?其實主要原因是:viewPath 的長度不固定,而且一般都會比較長,不便于后臺使用它作為 view 的唯一標識。因此 SDK 使用viewPath信息通過MD5加密生成一個固定長度的值作為viewId。

6. viewPath 與 viewId 重復時的解決方案

經過對viewPath的優(yōu)化,SDK 已經盡可能的保證了viewPath的穩(wěn)定性。但是并不表示只依靠viewPath就能區(qū)分所有的點擊事件。有時同一個viewPath的 view 具有不同的表現(xiàn)形式與作用,例如下面的情況:

  • 同一個按鈕在不同的狀態(tài)下,顯示不同的文字。例如:一個按鈕在未添加商品前顯示“添加”;添加了商品之后,立刻顯示成“清除”
  • 同一個view上具有多處點擊事件,例如 SegmentControl、UISwitchUIStepper

上面的這2種情況,都是同一個viewPath對應多個事件,此時如果只使用viewPath無法區(qū)分出不同的狀態(tài)或事件。

針對這類問題,SDK 的解決方案是:viewPath + “其它信息” 。這里的 “其它信息” 是視不同情況而定的,比如: 在上面的情況1中,“其它信息” 就是按鈕的 title。在情況2中,“其它信息” 是 SegmentControl 的 selectedIndex 和 UISwitch 的 isOn 屬性的值。SDK 在進行數(shù)據收集時,會上傳 view 的這些信息,再結合圈選SDK就能讓后臺在做統(tǒng)計時區(qū)分出這些不同的事件了。

關于“其它信息”,再補充一點,除了 SDK 事先知道要獲取的信息之外,還有一類就是業(yè)務數(shù)據。例如:有一個商品列表頁,每一行顯示一個商品,如果后臺想統(tǒng)計的不是列表中每一行的點擊,而是每個商品的點擊,那么此時的“其它信息”就應該是productId 了。關于 SDK 對業(yè)務層數(shù)據的獲取與上報請看下面的介紹。

SDK無埋點業(yè)務數(shù)據收集的實現(xiàn)

講完了 viewPath 之后,接下來詳細介紹下 SDK 的另一個關鍵技術:基于 viewPathKVC 實現(xiàn) SDK 的無埋點業(yè)務數(shù)據收集功能。首先,先簡單分析一下傳統(tǒng)的 代碼埋點 存在的缺點,大致有以下幾個:

  • 埋點代碼與業(yè)務邏輯代碼混合在一起,增加了代碼的維護成本;
  • 埋點代碼需要跟隨APP版本一起發(fā)布,耽誤數(shù)據的收集與統(tǒng)計;
  • 埋點時存在錯埋、漏埋等情況,無法動態(tài)更新及添加;

為了解決上述的 代碼埋點 的缺陷,SDK 實現(xiàn)了真正意義上的 無埋點 來對業(yè)務數(shù)據進行收集。

1. 無埋點的實現(xiàn)架構

SDK 的無埋點功能的實現(xiàn)主要依賴于 viewPathKVC。viewPath前面已經介紹了,它主要用于標識viewTree中的某個 view。而KVC對于 iOS 開發(fā)者也不陌生,堪稱 iOS 開發(fā)中的黑魔法之一。通過KVC我們能夠通過 key 或 keyPath 直接訪問對象的屬性,而不需要調用明確的存取方法。關于KVC如果不太了解,請自行學習,這里不再過多闡述。

那么如何實現(xiàn)不需要代碼埋點就能隨意獲取想要的業(yè)務數(shù)據呢?先看一下 SDK 的無埋點技術的整體架構圖:

從上圖可以看出,在實現(xiàn) SDK 的無埋點數(shù)據收集時,主要分為3步:上傳KVC配置、請求KVC配置、業(yè)務數(shù)據的收集與上報。

2. 什么是 KVC 配置

在上圖中出現(xiàn)了 KVC配置,那么下面先簡單介紹下什么是KVC配置。其實 KVC配置 就是一些用來描述 App 應該在什么時機去收集什么數(shù)據的信息,包含的主要信息有:

  • appKey:用來標識是哪個應用
  • appVersion:用來標識應用的版本號
  • viewEvent:標識某個事件類型(收集時機),例如:ButtonClick、ListItemClick、ViewTap等
  • viewPath:目標 view 在viewTree中的信息
  • keyPath:目標 view 與要收集的業(yè)務數(shù)據間的關聯(lián)路徑,用于KVC取值
  • keyName:為要收集的業(yè)務數(shù)據定義一個key,最終組成 key-value 的形式上報。用于區(qū)分多個收集的數(shù)據

3. KVC配置的上傳與下發(fā)

  • 上傳KVC配置

    • 利用 圈選SDK 上傳 KVC配置 的操作對于用戶是透明的,主要由開發(fā)人員進行上傳與管理。此操作可以在任何時候進行,在想要收集某個或某些版本的 App 中的業(yè)務數(shù)據時,上傳相應的KVC配置信息至后臺即可,達到了根據需要動態(tài)可配的效果。
  • 請求KVC配置

    • SDK 在初始化時會觸發(fā) KVC配置 的請求操作,從后臺拉取 App 當前版本對應的所有KVC配置,并將請求結果緩存起來,以提供給下一步使用。

4. 業(yè)務數(shù)據的收集與上報

這一部分是 SDK 無埋點技術的核心,接下來詳細介紹這部分的實現(xiàn)邏輯。它的實現(xiàn)流程如下:

這個環(huán)節(jié)的核心是基于viewPath的 view 匹配,主要實現(xiàn)是通過循環(huán)遍歷viewPath的每個節(jié)點的信息與當前 view 及其父view 依次進行匹配。因此這一步會產生一定的時間與性能消耗。為了盡可能減少這部分的操作,SDK 中使用了一些方式進行優(yōu)化,其中一個就是基于緩存view的優(yōu)化。

4.1 基于緩存view的優(yōu)化

SDK 采用緩存上一次匹配成功的 view 信息的方式,來減少一些不必要的viewPath匹配操作。這里主要緩存的 view 信息有:

  • targetView:上一次通過viewPath匹配成功的 view 對象。
  • indexPath: 上一次通過viewPath匹配成功的 view 的indexPath,如果沒有則為nil。
1. viewEvent 匹配

第一步先進行事件類型的匹配。如果KVC配置信息指定的 viewEvent 是 ButtonClick,那么可以輕松的過濾掉 ListItemClick、ViewTap 等其它事件。這一步能夠過濾一大部分事件,只有事件類型匹配成功才繼續(xù)進行下一步。

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的各個節(jié)點進行逐個匹配。由于是一個循環(huán)操作,因此會有一定的時間消耗,其實在這部分的匹配中,也做了一些簡單的優(yōu)化。在真正進入循環(huán)匹配之前,先進行如下3步判斷:

  • 判斷 view 類名是否相等;
  • 判斷 view 所在的 viewController 類名是否相等;
  • 判斷 view 所在的 window 類名是否相等;

上述的3個判斷也能過濾很多不必要的匹配。只有這3個判斷均通過后,才進行viewPath循環(huán)匹配。

5. KVC 取值與上報

到了這一步,就已經驗證了數(shù)據收集的時機是正確的。接下來就可以直接使用 KVC配置信息中的keyPath調用 valueForKeyPath: 方法獲取對應的值。如果值不為nil,就與 keyName 組成一個鍵值對,放到當前的事件數(shù)據中一起上報上去。這樣后臺就可以通過key去查找到相應的業(yè)務數(shù)據了。

上面只是簡要介紹了一下匹配時的邏輯,在實際開發(fā)中還會添加對 cell 的indexPath通配的情況的處理,由于文章篇幅這里不再詳細講解。

5. 增加對 KVC 的異常處理

SDK 的無埋點功能的實現(xiàn)其實主要依賴于KVC,但是眾所周知,KVC是非常危險的,很容易造成程序崩潰。例如一旦 key 或 keyPath 所對應的屬性名不存在,立刻會導致程序拋出一個NSUndefinedKeyException異常,如果應用沒有處理此異常,程序就會Crash。

因此,為了避免程序Crash,SDK 內部增加了對KVC異常的處理。具體實現(xiàn)是給 NSObject 增加一個 Category ,重寫 valueForUndefinedKey: 方法,并在方法中return nil。

@implementation NSObject (KVCExceptionHandler)
- (nullable id)valueForUndefinedKey:(NSString *)key
{
    return nil;
}
@end

其它關鍵技術

當然,SDK 的實現(xiàn)中還有很多關鍵技術點,比如:SDK 對 RN 頁面的數(shù)據收集、頁面別名方案的實現(xiàn)、Method SwizzlingAspects的兼容等。由于本文的篇幅已經很長了,而且考慮到大家讀文章的耐性都不會太長,所以這里就先不講解了,后續(xù)會再寫文章單獨介紹。

END

文章寫了這么多,其實主要介紹了 SDK 中的兩個關鍵技術點,希望對你們能有一些參考價值。另外,如果有人對本文的方案有更好的建議,歡迎一起討論學習。

最后,要特別感謝我的同事王佳樂,由于他對文章的排版與校對工作,才使得本文能更好的展示給大家。同時也要感謝組內的所有同事,在我開發(fā)遇到困難時,給予了我很多的幫助。

Q & A

關于對本文內容提出的一些問題,將全部記錄在這里(簡書評論里的除外),并進行統(tǒng)一解答。

Q1: SDK 都使用KVC配置獲取業(yè)務數(shù)據,是否會增加維護KVC配置的工作?

A1: 會有對 KVC配置 的維護與管理工作,不過 SDK 也簡化了這塊的管理工作。

一般來說,上傳的所有的 KVC配置 需要與 App 的版本相對應,因為 App 版本不同會直接導致keyPath可能不一樣。所以與 KVC配置 相關的工作有如下2個:

  1. 針對當前 App 版本上傳相應的 KVC配置,以獲取想要的業(yè)務數(shù)據
  2. 當 App 新版本發(fā)布時,需要對之前版本上的 KVC配置 逐一驗證,是否仍然適用于新版本。如果仍然適用,則直接在管理后臺上把新的版本號添加到此 KVC配置;如果不再適用,則對新版本再上傳一個新的KVC配置。

從上面可以看出,在 App 版本不斷迭代的過程中,KVC配置 會越來越多,相應的維護與管理工作也相當繁瑣。

為了解決這個痛點,SDK 中增加了一種方案來避免這種重復且繁瑣的工作。具體的方案是:

  • 在上傳 KVC 配置時,指定某個區(qū)間的版本,或者不指定具體的版本(即應用到當前所有版本上);
  • SDK 在使用KVC配置獲取業(yè)務數(shù)據失敗時,添加相關的錯誤日志,并上報上去。其中錯誤日志里包含了appKeyappVersion、keyPath等信息,這樣就能在后臺清晰的看到哪些 KVC配置 在哪個 App 版本上存在問題;
  • 使用腳本監(jiān)控與KVC相關的錯誤日志。如果監(jiān)控到有錯誤日志上報,則發(fā)送郵件通知給相關人員;

因此,SDK 采用此方案優(yōu)化之后,KVC配置 的管理工作就只有1個了:

  • 根據Log信息快速找到對應的 KVC配置,并上傳一個針對新版本的 KVC配置

Q2: 對于 “內容與位置” 可能會隨時間而變動時,如何實現(xiàn)數(shù)據收集與統(tǒng)計?

A2: 使用圈選SDK與數(shù)據SDK共同完成動態(tài)數(shù)據的收集與統(tǒng)計

這個問題在實際產品中也比較常見,比如 App 首頁的內容大多是通過后臺配置的。
這個問題其實可以轉化或分解成如下的2個情況:

  • 同一位置會顯示不同的內容
  • 同一內容會顯示在不同的位置

注意,這2個并非同一個,它們分別對應于不同的場景,同時數(shù)據收集的方案也有所不同。

另外,“位置” 可以是在列表中,也可以是非列表中的,不過這個對整體的方案沒有太大影響,僅僅是在不關心位置時viewPath中的通配符位置不同。

A2.1 同一位置顯示不同的內容

例子:在 App 首頁有一個展示最近活動的位置,先展示活動1的圖片,過一段時間運營人員又配成活動2的圖片。如何統(tǒng)計活動1、活動2各自的點擊量?

針對這種場景,SDK 的解決方案是:“關心位置” + “關心內容”。
“關心位置” 的意思是只使用當前的位置,具體表現(xiàn)是viewPath中不包含任何通配符;“關心內容” 的意思是指定一個想要統(tǒng)計的內容。

整個過程可以分解為如下3個環(huán)節(jié):

  • 圈選SDK上傳“關心位置”的KVC配置。KVC配置中指定獲取活動的urlkeyPath。
  • 數(shù)據SDK在活動發(fā)生點擊時,收集當前活動對應的url,并跟隨點擊事件一起上報。
  • 圈選SDK上傳“關心位置” + “關心內容”的圈選配置,關心的內容指定為想要統(tǒng)計的活動的url值。
A2.2 同一內容顯示在不同的位置

例子:App 首頁有4個固定的入口,假設其中一個叫“熱門推薦”,那么根據后臺配置的順序不同,“熱門推薦”可能被顯示在4個位置中的任何1個,即一段時間顯示在第1個,過一段時間可能顯示在第2個位置。這時如何統(tǒng)計出“熱門推薦”的點擊量?

針對這種場景,SDK 的解決方案是:“不關心位置” + “關心內容”
“不關心位置” 是指viewPath中含有通配符,用于表示viewTree中的多個位置。例如想要匹配列表所有行時,則將viewPath中的indexPath替換為通配符。

這個問題的解決過程也分為如下3步:

  • 圈選SDK上傳“不關心位置”的KVC配置。KVC配置中指定獲取入口的 title 的keyPath。
  • 數(shù)據SDK在4個中任何一個入口被點擊時,都去收集入口的 title,并跟隨點擊事件一起上報。
  • 圈選SDK上傳“不關心位置” + “關心內容”的圈選配置,關心的內容指定為“熱門推薦”。

到這里,數(shù)據收集與圈選配置的工作都已經做完了,接下來就是后臺的數(shù)據統(tǒng)計了。
上述2種情況對后臺進行統(tǒng)計沒有區(qū)別,都使用一個統(tǒng)計方案,這里也介紹一下后臺大概的統(tǒng)計思路:

  • 拿到第3步中上傳的圈選配置,根據viewPath“關心的內容” 生成一個正則表達式,然后從數(shù)據 SDK 上報的原始數(shù)據中進行正則匹配,進而統(tǒng)計出相應數(shù)據。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容