iOS全埋點解決方案-控制項點擊事件

来源:https://www.cnblogs.com/r360/archive/2022/04/06/16105309.html
-Advertisement-
Play Games

前言 ​ 我們主要介紹如何實現控制項點擊事件($AppClick)的全埋點。在介紹如何實現之前,我們需要先瞭解一下,在 UIKit 框架下,處理點擊或拖動事件的 Target-Action 設計模式。 一、 Target-Action ​ Target-Action,也叫目標-動作模式,即當某個事件發 ...


前言

​ 我們主要介紹如何實現控制項點擊事件($AppClick)的全埋點。在介紹如何實現之前,我們需要先瞭解一下,在 UIKit 框架下,處理點擊或拖動事件的 Target-Action 設計模式。

一、 Target-Action

​ Target-Action,也叫目標-動作模式,即當某個事件發生的時候,調用特定對象的特定方法。

​ 比如,在 LoginViewController 頁面,有一個按鈕,點擊按鈕時,會調用 LoginViewController 里的 - loginBtnOnClick 方法,“特定對象”就是 Target,“特定方法”就是 Action。也即 Target 是 LoginViewController, Action 是 - loginBtnOnClick 方法。

Target-Action 設計模式主要包含兩個部分:

  • Target 對象:接收消息的對象
  • Action 方法:用於表示需要調用的方法

​ Target 對象可以是任意類型的對象。但是在 iOS 應用程式中,通常情況下會是一個控制器,而觸發事件的對象和 Target 對象一樣,也可以是任意對象。例如,手勢識別器 UIGestureRecognizer 就可以在識別到手勢後,將消息發送給另一個對象。Target-Action 設計模式,最常見的應用場景還是在控制項中。iOS 中的控制項都是 UIControl 類或者其子類,當用戶在操作這些控制項時,會將消息發送到指定的對象(Target),而對應的 Action 方法必須符合以下幾種形式之一 :

- (void)doSomething;
- (void)doSomething:(id)sender;
- (void)doSomething:(id)sender forEvent:(UIEvent *)event;
- (IBAction)doSomething;
- (IBAction)doSomething:(id)sender;
- (IBAction)doSomething:(id)sender forEvent:(UIEvent *)event;

​ 其中以 IBAction 作為返回值類型的形式,是為了讓該方法能在 Interface Builder 中被看到;sender 參數就是觸發事件的控制項本身;第二個參數 event 是 UIEvent 的對象,封裝了觸摸事件的相關信息。我們可以通過代碼或者 Interface Builder 為一個控制項添加一個 Target 對象以及相對應的 Action 方法。

​ 若想使用代碼方式添加 Target-Action(我們也會用 Target-Action 表示:一個 Target 對象以及相對應的 Action 方法),可以直接調用控制項對象的如下方法:

- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

我們也可以多次調用 - addTarget:action:forControlEvents: 方法給控制項添加多個 Target-Action,即使多次調用- addTarget:action:forControlEvents: 添加相同的 Target 但是不同的 Action,也不會出現相互覆蓋的問題。另外,在添加 Target-Action 的時候,Target 對象也可以為 nil(預設會先在 self 里查找 Action)。

當我們為一個控制項添加 Target-Action 後,控制項又是如何找到 Target 對象並執行對應的 Action 方法的呢?

在 UIControl 類中有一個方法:

- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;

如果控制項被用戶操作(比如點擊),首先會調用這個方法,並將事件轉發給應用程式的 UIApplication 對象。

同時,在 UIApplication 類中也有一個類似的實例方法:

- (BOOL)sendAction:(SEL)action to:(nullable id)target from:(nullable id)sender forEvent:(nullable UIEvent *)event;

如果 Target 對象不為 nil,應用程式會讓該 Target 對象調用對應的 Action 方法響應事件;如果 Target 對象為 nil,應用程式會在響應者鏈中搜索定義了該方法的對象,然後執行 Action 方法。

基於 Target-Action 設計模式,我們有兩種方案可以實現 $AppClick 事件的全埋點。

二、實現方案

​ 通過 Target-Action 執行模式可知,在執行 Action 方法之前,會先後通過控制項和 UIApplication 對象發送事件相關的信息。因此,我們可以通過 Method Swizzling 交換 UIApplication 的 - sendAction:to:from:forEvent: 方法,然後在交換後的方法中觸發 $AppClick 事件,並根據 target 和 sender 採集相關的屬性,即可實現 $AppClick 事件的全埋點 。

​ 對於 UIApplication 類中的 - sendAction:to:from:forEvent: 方法,我們以給 UIButton 設置 action 為例,詳細介紹一下。

[button addTarget:person action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];

參數:

  • action:Action 方法對應的 selector,即示例中的 btnAction。
  • target:Target 對象,即示例中的 person。如果 Target 為 nil,應用程式會將消息發送給第一個響應者,並從第一個響應者沿著響應鏈向上發送消息,直到消息被處理為止。
  • sender:被用戶點擊或拖動的控制項,即發送 Action 消息的對象,即示例中的 button。
  • event:UIEvent 對象,它封裝了觸發事件的相關信息。

返回值:

如果有 responder 對象處理了此消息,返回 YES,否則返回 NO。

2.1 實現步驟

​ 通過 Method Swizzling 交換 UIApplication 類中的 -sendAction:to:from:forEvent: 方法來實現 $AppClick 事件的全埋點。

第一步:創建 UIApplication 分類 UIApplication+SensorsData

第二步:實現交換方法 -sensorsdata_sendAction:to:from:forEvent:

z#import "SensorsAnalyticsSDK.h"

- (BOOL)sensorsdata_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
    // 觸發 $AppClick 事件
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    [[SensorsAnalyticsSDK sharedInstance] track:@"$AppClick" properties:properties];
    
    // 調用原有的實現 即 sendAction:to:from:forEvent:
    return [self sensorsdata_sendAction:action to:target from:sender forEvent:event];
}

第三步:實現 load 類方法,併在類方法中實現 - sendAction:to:from:forEvent: 方法交換

#import "NSObject+SASwizzler.h"

+ (void)load {
    [UIApplication sensorsdata_swizzleMethod:@selector(sendAction:to:from:forEvent:) withMethod:@selector(sensorsdata_sendAction:to:from:forEvent:)];
}

第四步:測試驗證,在Demo 中添加 button 按鈕,點擊按鈕

{
  "event" : "$AppClick",
  "time" : 1648696085563,
  "propeerties" : {
    "$model" : "x86_64",
    "$manufacturer" : "Apple",
    "$lib_version" : "1.0.0",
    "$os" : "iOS",
    "$app_version" : "1.0",
    "$os_version" : "15.2",
    "$lib" : "iOS"
  }
}

2.2 優化 $AppClick 事件

一般情況下,對於一個控制項的點擊事件,我們至少還需要採集如下信息(屬性):

  • 控制項類型($element_type)
  • 控制項上顯示的文本($element_content)
  • 控制項所屬頁面,即 UIViewController($screen_name)

基於目前的方案,我們來看如何實現採集以上三個屬性。

1、獲取控制項類型

​ 獲取控制項類型相對比較簡單,我們可以直接使用控制項的 class 名稱來代表當前控制項的類型,比如可通過如下方式獲取控制項的 class 名稱:

NSString *elementType = NSStringFromClass([sender class]);

2、獲取顯示屬性

​ 需要根據特定的控制項調用相應的方法。

第一步:在 UIView 的類別 SensorsData 中新增 sensorsdata_elementContent 屬性。

@interface UIView (SensorsData)

@property (nonatomic, copy, readonly) NSString *sensorsdata_elementType;

@property (nonatomic, copy, readonly) NSString *sensorsdata_elementContent;

@end

- (NSString *)sensorsdata_elementContent {
    return nil;
}

第二步:在 UIView+SensorsData 分類中新增 UIButton 的類別 SensorsData,並實現 -sensorsdata_elementContent 方法

#pragma mark - UIButton
@interface UIButton (SensorsData)

@end
@implementation UIButton (SensorsData)

- (NSString *)sensorsdata_elementContent {
    return self.titleLabel.text;
}

@end

第三步:修改 SensorsAnalyticsSDK+Track 中 - trackAppClickWithView: properties: 方法

- (void)trackAppClickWithView:(UIView *)view properties:(nullable NSDictionary <NSString*, id> *)properties {
    // 觸發 $AppClick 事件
    NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary];
    // 獲取控制項類型
    [eventProperties setValue:view.sensorsdata_elementType forKey:@"$element_type"];
    // 獲取控制項文本
    [eventProperties setValue:view.sensorsdata_elementContent forKey:@"$element_content"];
    [eventProperties addEntriesFromDictionary:properties];
    [[SensorsAnalyticsSDK sharedInstance] track:@"$AppClick" properties:eventProperties];    
}

第四步:測試驗證

{
  "event" : "$AppClick",
  "time" : 1648708284842,
  "propeerties" : {
    "$model" : "x86_64",
    "$manufacturer" : "Apple",
    "$element_type" : "UIButton",
    "$lib_version" : "1.0.0",
    "$os" : "iOS",
    "$element_content" : "eeeeeee",
    "$app_version" : "1.0",
    "$os_version" : "15.2",
    "$lib" : "iOS"
  }
}

3、獲取控制項所屬的界面

如何知道一個 UIView 所屬哪個 UIViewController 呢?

這就需要藉助 UIResponder 了!

大家都知道,UIResponder 類是 iOS 應用程式中專門用來響應用戶操作事件的,比如:

  • Touch Events:即觸摸事件
  • Motion Events:即運動事件
  • Remote Control Events:即遠程式控制制事件

​ UIApplication、UIViewController、UIView 類都是 UIResponder 的子類,所以它們都具有響應以上事件的能力。另外,自定義的 UIView 和自定義視圖控制器也都可以響應以上事件。在 iOS 應用程式中,UIApplication、UIViewController、UIView 類的對象也都是一個個響應者,這些響應者會形成一個響應者鏈。一個完整的響應者鏈傳遞規則(順序)大概如下:UIView → UIViewController → RootViewController → Window → UIApplication → UIApplicationDelegate,可參考下圖所示(此圖來源於蘋果官方網站) 。

image-20220331145258636

​ 註意:對於 iOS 應用程式里實現了 UIApplicationDelegate 協議的類(通常為 AppDelegate),如果它是繼承自 UIResponder,那麼也會參與響應者鏈的傳遞;如果不是繼承自 UIResponder(例如 NSObject),那麼它就不會參與響應者鏈的傳遞。

​ 通過圖可以知道,對於任意一個視圖來說,都能通過響應者鏈找到它所在的視圖控制器,也就是其所屬的頁面,從而可以達到獲取它所屬頁面信息的目的。

第一步:新增 sensorsdata_viewController 屬性

@interface UIView (SensorsData)

@property (nonatomic, copy, readonly) NSString *sensorsdata_elementType;

@property (nonatomic, copy, readonly) NSString *sensorsdata_elementContent;

@property (nonatomic, copy, readonly) NSString *sensorsdata_viewController;

@end

第二步:實現 實現 -sensorsdata_viewController 方法

- (NSString *)sensorsdata_viewController {
    UIResponder *responder = self;
    while ((responder = [responder nextResponder])) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder.class;
        }
    }
    return nil;
}

第三步:修改 - trackAppClickWithView: properties: 方法

- (void)trackAppClickWithView:(UIView *)view properties:(nullable NSDictionary <NSString*, id> *)properties {
    // 觸發 $AppClick 事件
    NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary];
    // 獲取控制項類型
    [eventProperties setValue:view.sensorsdata_elementType forKey:@"$element_type"];
    // 獲取控制項文本
    [eventProperties setValue:view.sensorsdata_elementContent forKey:@"$element_content"];
    // 獲取控制項所在的控制器
    UIViewController *vc = view.sensorsdata_viewController;
    [eventProperties setValue:NSStringFromClass(vc.class) forKey:@"$screen_name"];
    [eventProperties addEntriesFromDictionary:properties];
    [[SensorsAnalyticsSDK sharedInstance] track:@"$AppClick" properties:eventProperties];    
}

第四步:測試驗證

{
  "event" : "$AppClick",
  "time" : 1648711998403,
  "propeerties" : {
    "$model" : "x86_64",
    "$manufacturer" : "Apple",
    "$element_type" : "UIButton",
    "$lib_version" : "1.0.0",
    "$os" : "iOS",
    "$element_content" : "eeeeeee",
    "$app_version" : "1.0",
    "$screen_name" : "ViewController",
    "$os_version" : "15.2",
    "$lib" : "iOS"
  }
}

三、遺留問題

如果,一個控制項添加了多個 Target-Action,會導致多次觸發 $AppClick 事件。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 歸併排序演算法簡介 歸併排序演算法是一種採用了分治策略的排序演算法。它通過遞歸地先使每個子序列有序,再將兩個有序的序列進行合併成一個有序的序列(也可以採用非遞歸的方式實現,效率更高一些)。歸併演算法是穩定和高效的排序演算法(適用於複雜對象(結構體)數列的穩定排序) 二. 演算法複雜度 最理想情況:O(nl ...
  • 話不多說 先上效果圖:功能:1.每一個更新按鈕對應著所更新的書籍定價數量,並將結果輸出至消費總金額處2.提交對應著所有書籍對應價格數量的總和,同樣輸出至總金額處3.重置既刷新頁面實列:1.整體是居中的,效果圖存在位置偏差2.數量和消費總金額的預設值是0,如果將0刪除會出現報錯,所以請小心OK 上代碼 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 在本文中,我們將瞭解如何在 Ubuntu 20.04 上安裝 Kubernetes。在過去的幾年裡,容器化為開發人員提供了很大的靈活性。最常用的容器化應用程式之一是 Docker。 運行小型應用程式並不難,但如果你想擴展它們怎麼辦?當您擁有成百上 ...
  • 背景:WindowsServer2019安裝.NET3.5報錯0x800f0950,嘗試網上的方法,發現以下方法有效,進行重新整理解決方法,進行記錄。 一、以下為報錯信息: 二、嘗試過的方法有: 直接從伺服器控制面板安裝。 使用dism命令安裝,存在如下報錯: C:\Windows>dism /on ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、 安裝依賴包 # yum –y install gcc gcc-c++ openssl openssl-devel pcre pcre-devel zlib zlib-devel 如yum安裝依賴包時報錯: 解決辦法: #~ wget -O ...
  • 一、概述 Hive是基於Hadoop的一個數據倉庫(Data Aarehouse,簡稱數倉、DW),可以將結構化的數據文件映射為一張資料庫表,並提供類SQL查詢功能。是用於存儲、分析、報告的數據系統。 在Hadoop生態系統中,HDFS用於存儲數據,Yarn用於資源管理,MapReduce用於數據處 ...
  • 本文是 OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)標準設備應用開發的第一篇文章。這一篇我們主要聚焦於如何在標準設備上運行一個最簡單的 OpenHarmony 程式。 ...
  • OpenHarmony 開源開發者成長計劃第二期知識賦能直播課程以入門為主,共設置 8 節課,覆蓋了應用開發、設備開發、內核驅動等多個技術領域。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...