從 0 到 1 搭建技術中臺之 iOS 可視化埋點實踐

自去年開始,中臺話題的熱度不減,很多公司都投入到中臺的建設中,從戰略制定、組織架構調整、協作方式變動到技術落地實踐,每個環節都可能出現各種各樣的問題。技術中臺最壞的狀況是技術能力太差,不能支撐業務的發展,其次是技術脫離業務,不能服務業務的發展。前者是能力問題,後者是意識問題。在本專題中,伴魚技術團隊分享了從 0 到 1 搭建技術中臺的過程及心得。

前言

可視化埋點,也稱圈選埋點,是建立在全埋點技術基礎上的一種數據埋點機制。通過全埋點技術,儘可能地將用戶的所有交互行爲進行採集上報,然後通過可視化圈選的方式篩選出感興趣的行爲統計數據,爲產品運營提供決策支持。可視化埋點具有“全面、便捷、低技術門檻”的特點,能夠有效降低研發、運營成本,是對傳統代碼埋點技術的有力補充。

本文結合伴魚iOS端在圈選埋點技術上的一些實踐經驗,對圈選埋點方案的設計和實現進行探討。

總體思路

從數據採集到生成統計報表,一般需要經過三個步驟,如下圖所示

main frame

1.用戶行爲數據採集:通過全埋點技術採集用戶行爲事件;

2.圈選配置匹配規則:由產品或運營人員通過可視化圈選工具,對感興趣的用戶行爲事件進行標定,生成事件匹配規則,並上傳到服務端;

3.匹配計算生成報表:數據研發人員根據已配置的事件匹配規則進行數據統計,生成報表

這裏採用全埋點的方式採集用戶行爲數據,會增加 App 端數據流量和服務端數據存儲壓力。選擇該方案的理由參見4.2 前後端配合方式的選擇 。

事件標識定義

全埋點採集用戶行爲,需要解決的最大問題是:如何精確描述行爲事件。通常對頁面和頁面中的可交互元素分別進行定義。

A. 頁面標識

頁面標識通常採用 2 種方式來標定:

1.頁面路徑:從 Window 的根控制器開始直到頁面所在視圖控制器的路徑。例如

UITabBarController-UINavigationController(1)-MyViewController(2)

括號中的數字代表控制器在父控制器中的索引。

2.頁面類名: 直接已控制器的類名作爲頁面標識。被 Presented 的控制器也適用於該方式。

例外情況

a. 頁面所屬控制器存在自定義的父控制器

例如:一個控制器包含了若干子控制器,且通過 UIScrollView 分頁的方式呈現各子控制器的視圖。對於此類控制器,無法通過 hook viewDidAppear: 的方式來記錄PV。

解決辦法:通過 UIScrollViewDelegate 的 scrollViewDidScroll: 和 scrollViewDidEndScrollingAnimation: 方法來監聽 UIScrollView 的內容偏移事件,根據 contentOffset 計算當前顯示的視圖屬於哪一個控制器,最後手動觸發控制器的 viewDidAppear: 方法。

b. 一些頁面需要避免被採集

一些用於調試的頁面,或經產品確認不參與採集的頁面,通過下發 ignore list 的方式來過濾。

B. 元素標識

理論上,頁面中所有可交互的元素都應該能夠被採集到。但考慮到 App 交互的多樣性和現實成本,這裏僅討論支持點擊操作的元素。

通常,元素標識由三部分組成

1.元素在頁面視圖樹中的路徑

路徑由視圖樹根節點開始,到該元素節點的父節點爲止。例如:假設視圖中有一個按鈕控件,那麼它的路徑可以表示成如下形式:

UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView

2.元素的類型名稱+索引

以上述按鈕爲例:它的類型名爲UIButton,索引爲其在父視圖中的添加順位。

3.元素的內容

元素的內容可能是文本、圖片、其他包含圖片或文字的子元素組合。類似於 UILabel、UIImageView 這樣的元素,直接獲取其文本信息或圖片URL即可。對於 UIButton,獲取其 currentTitle 文本或 UIControlStateNormal 狀態下的圖片 URL。文本內容優先於圖片內容。

對於圖文並存,或包含子元素組合的元素,需要根據元素類型及圈選方式確定,且元素內容標識需要單獨生成。在 元素內容 一節中有詳細介紹。

上述按鈕的完整標識可以表示如下:

UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UIButton(0)_[click me]

UIButton後面小括號中的數字”0”表示其在父視圖中的索引,中括號內的 “click me” 來自其 currentTitle 的值。

元素索引的添加時機

建議只從視圖控制器所在的視圖開始添加元素索引。系統內置的視圖,如 UITransitionView 會在運行時修改其子元素的索引,造成元素路徑發生變化,因此在進行路徑追溯時,到達 UIViewController (注意不含 UITabBarController 和UINavigationController) 就不再添加索引。

獨立元素與可重複元素的路徑

獨立元素是指在視圖中獨立繪製的元素,通常與其他元素無關聯。對於此類型元素,標識定義爲:”路徑”“類型+索引”[“內容”]。

可重複元素是指在列表中繪製的元素。在 iOS 中只考慮 UITableViewCell 和 UICollectionReusableView。通過元素在父視圖中的 indexPath 來確定元素的索引,即 (indexPath.section-indexPath.row),那麼可重複元素的路徑可以定義爲:

... UIView-UITableView(0)-UITableViewCell(indexPath.section-indexPath.row)

元素內容

我們將元素內容的分爲圖片和文本兩類。文本類內容可以從控件的 text、title 等屬性獲取,這裏不再贅述。圖片內容的獲取,有2種方式:

通過 imageNamed: 方法設置的圖片,通過 description 方法打印其信息,可以得到類似如下結果:

<UIImage:0x6000005de2e0 named(main: home_search_icon) {24, 24}>

這裏的 “main: home_search_icon” 表示圖片名稱爲 “home_search_icon”,來自 “main bundle”。我們可以截取 “main: home_search_icon” 作爲圖片內容。

如果通過 description 方法打印的信息如下:

<UIImage:0x6000005a1f80 anonymous {75, 75}>

這說明圖片是通過其他方式進行設置的,需要通過第二種方式來獲取其內容。

  • 通過 SDWebImage 等三方庫設置了 UIImageView 的 URL,可以直接在運行時獲得其關聯屬性
NSString *imageContent;
SEL sel = NSSelectorFromString(@"sd_imageURL");
if ([self respondsToSelector:sel]) {
    NSURL *url = [self performSelector:sel];
    if ([url isKindOfClass:[NSURL class]])
    {
        imageContent = url.absoluteString;
    }
}

單一內容、複合內容以及內容標識

如果一個元素只含有一項文本或圖片,則稱這個元素的內容爲單一內容。單一內容本身作爲其內容標識。

如果一個元素包含多個文本或圖片、或其子元素內也包含文本或圖片,則稱其內容爲複合內容。我們對複合內容進行遍歷,遍歷結果按鍵值對保存:

{
	"UIView-UILabel(0)": "text 1",
	"UIView-UIImageView(1)": "main: search_icon",
}

其中,key 對應的是子元素相對路徑,作爲改內容的內容標識,即從當前元素到子元素的路徑,value 對應的是該內容具體的文本或圖片內容。

對於具有複合內容的元素,有時會對其中某一項內容進行統計,該內容的內容標識可以參與到事件匹配。

考慮到性能影響,一個元素的內容遍歷深度一般不超過5。

事件匹配規則

我們通過定義事件匹配規則來對事件進行過濾,符合匹配規則的事件被認爲是需要進行統計的。匹配規則實質上是對頁面標識、元素標識、元素內容定義的一系列正則表達式。將用戶行爲相關的頁面、元素標識、元素內容與事先定義的正則表達式進行匹配,匹配成功則進行統計。

正則表達式符號定義:

爲了簡化正則表達式的書寫,我們將正則表達式中需要精確匹配的字符串進行如下約定:

  • fixedPrefix:表示固定的前綴字符,元素的路徑需要精確匹配
  • fixedSuffix:表示固定的後綴字符,元素的索引或其他需要精確匹配的字符
  • fixedStr:表示固定的完整字符,元素的標識或內容需要精確匹配
  • fixedSection:在可重複元素中表示固定的 section,可重複元素的 section 索引需要精確匹配
  • fixedRow:在可重複元素中表示固定的 row,可重複元素的 row 索引需要精確匹配

假設我們要採集一個元素的標識爲

UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell(1-2)_[null]

根據上述約定,我們可以定義如下正則表達式來採集該元素:

^UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell(1-2)_[[\S|\s]+\]
用fixedPrefix表示”UIWindow-UITransitionView-UIDropShadowView-UILayoutContainerView-UITransitionView-
UIViewControllerWrapperView-UILayoutContainerView-UINavigationTransitionView-
UIViewControllerWrapperView-UIView-UITableView-UITableViewCell”

用 (fixedSection-fixedRow) 表示 “(1-2)”

用 fixedSuffix 表示 “_”

可以得到簡化後的正則表達式

^fixedPrefix\(fixedSection-fixedRow\)fixedSuffix\[[\S|\s]+\]$

該規則表示:匹配某個視圖列表中 section 爲 1,row 爲 2 的元素,不關注內容變化。

基於正則表達式的事件匹配規則

  • 頁面匹配規則

根據頁面標識進行精確匹配即可。

  • 獨立元素匹配規則

    • 當前位置
      關注元素的絕對位置,不關注元素內容。如果元素位置發生變化,則不納入統計。元素標識匹配正則表達式爲:^fixedPrefix[[\S|\s]+]$。
    • 當前內容
      關注元素的內容,允許元素在其父視圖內的索引發生變化。如果元素內容發生變化,則不納入統計。單一內容的元素標識匹配正則表達式爲:^fixedPrefix(\d*)fixedSuffix$,其中 fixedSuffix 直接涵蓋了元素內容;複合內容的元素標識匹配正則表達式爲:^fixedPrefix(\d*)[[\S|\s]+]$,且內容標識正則表達式爲:^fixedStr$。
  • 可重複元素匹配規則

    • 不關注內容
      • 同類元素
        關注列表中同一 section 內的所有元素。當用戶點擊任一元素時產生的事件都會納入統計。元素標識匹配正則表達式爲:^fixedPrefix(fixedSection-\d*)fixedSuffix[[\S|\s]+]$。
      • 當前位置
        只關注列表中固定位置的某個元素。只有當用戶點擊該元素時產生的事件纔會納入統計。元素標識匹配正則表達式爲:^fixedPrefix(fixedSection-fixedRow)fixedSuffix$。
    • 關注內容
      • 同類元素
        關注列表中同一 section 內的所有元素,且對指定內容進行聚合統計。元素標識匹配正則表達式與不關注內容的表達式一致:^fixedPrefix(fixedSection-\d*)fixedSuffix[[\S|\s]+]$。內容標識正則表達式爲: ^fixedPrefix[[\S|\s]+]$。
      • 當前位置
        只關注列表中固定位置的某個元素。只有當用戶點擊該元素時產生的事件纔會納入統計,並且對當前位置元素的指定內容進行統計聚合。元素標識匹配正則表達式爲:^fixedPrefix(fixedSection-fixedRow)fixedSuffix$。內容標識正則表達式爲 ^fixedPrefix[[\S|\s]+]$ 。該規則適用這樣的場景:運營人員想查看列表指定元素的內容對點擊率的影響。
      • 當前內容
        只關注列表中固定位置的某個元素,且該元素的某項內容不能發生改變。位置和內容任意一項發生變化,則不納入統計。元素標識匹配正則表達式爲:^fixedPrefix(fixedSection-fixedRow)fixedSuffix$。內容標識正則表達式爲^fixedPrefix$。
  • 元素、內容匹配規則表

前後端配合方式的選擇

前端匹配

  • 工作方式

    • 圈選配置由服務端統一下發到 App
    • App 根據圈選配置進行匹配採集,將採集到的用戶事件上報服務端
    • 服務端進行數據統計處理,生成報表
  • 優點

    • App 只上報被圈選匹配的事件,上報數據量小
    • 服務端只負責圈選配置的下發同步,實現較爲簡單
  • 缺點

    • 數據統計具有滯後性,依賴圈選配置下發的覆蓋程度
    • 無法追溯歷史,即無法統計圈選配置生效前發生的事件
    • App 端需要考慮匹配計算對性能的影響

後端匹配

  • 工作方式

    • App 全量採集用戶行爲事件
    • 服務端根據圈選配置,結合全量採集的事件進行匹配過濾
  • 優點

    • 可以支持實時統計
    • 可追溯歷史,即可以統計圈選配置生效前的歷史數據
    • App 不做匹配過濾,對性能無影響
  • 缺點

    • App全量採集的數據量大,需考慮對用戶流量的影響
    • 服務端做匹配過濾工作涉及的計算量較大
    • 服務端存儲全量採集數據涉及到的存儲空間較大

伴魚的選擇

  • 儘可能不影響用戶體驗。如果匹配規則較多,在端上做匹配過濾勢必會對性能有所影響。因此僅在提供了圈選配置功能的App上支持前端匹配功能。
  • 實時性、可追溯這兩個特性,對於產品和運用來說異常重要,不能妥協。
  • 全埋點採集的數據對於用戶流量的影響並不高。根據伴魚繪本的經驗,單個用戶平均一天產生的行爲數據不超過5M,相當於上傳了一張高清圖片。
  • 服務端的存儲資源可以定期清理。
  • 服務端的計算資源的問題可以通過彈性擴容的方式解決。

圈選驗證

產品或運營完成圈選配置後,需要驗證圈選是否生效。App 可以通過集成圈選SDK來實現所見即所得的驗證方式。如下圖所示,符合匹配規則的頁面和元素會以不同顏色高亮顯示。

元素標識發生變化導致匹配規則失效時如何處理?

無論何種原因導致元素的路徑或內容發生變化,最終會使得元素事件無法被事先配置的圈選規則匹配。有 2 種典型場景:

  • 產品需求迭代過程中的頁面改版導致元素路徑或內容發生了變更。在這種場景下,舊的圈選配置仍然生效,只需在新版本下手工增加新的圈選規則即可。 服務端進行數據統計時,可以按照 App 的版本進行區分。當 App 迭代到一定程度,較早前版本的圈選配置將失效。服務端可以定期對老版本的圈選配置進行清理。

  • App 在運行過程中,因業務條件發生變化導致頁面佈局或元素內容發生變更。例如,某些頁面對於付費用戶和非付費用戶展示不同的佈局效果。這其實和上述場景類似,需要在所有可能的用戶場景下分別進行圈選配置操作。

某些元素的父視圖層級固定,只是索引會發生變化,例如導航欄右上角的下拉菜單列表,列表中的元素順序可能會變化,但都限定在菜單容器內。對於這種元素,我們可以在生成圈選配置時,限定元素的文本內容。只要內容不變,仍然能夠被匹配命中。

總而言之,如果導致元素的標識變化的場景是可以被枚舉的,我們只需枚舉所有感興趣的場景,然後分別進行圈選埋點;如果元素的視圖層級固定,僅索引會變,我們可以根據元素內容進行限定,只匹配特定內容的元素;其他情況下建議直接使用代碼埋點。

總結

本文嘗試闡述這樣一種理念:通過全埋點的方式採集用戶行爲,通過正則匹配的方式構建統計規則,最終爲產品決策提供數據支持。圈選埋點技術有效地提高了研發效率,讓產品和運營能夠更直觀便捷地定義指標;但對於複雜的業務場景,代碼埋點仍然不可或缺。

參考:

GrowingIO JavaScript SDK 的實現細節和擴展
網易HubbleData無埋點SDK在iOS端的設計與實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章