前言
? 我們主要介紹如何實作控制元件點擊事件($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,可參考下圖所示(此圖來源于蘋果官方網站) ,

? 注意:對于 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 事件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/457076.html
標籤:其他
