构建 iOS 埋点监控 SDK
构建 iOS 埋点监控 SDK
设计目标
埋点监控 SDK 的核心目标:
- 事件采集:支持手动埋点和自动埋点。
- 数据缓存:本地存储未上传的数据,防止网络异常时丢失。
- 上传机制:批量上传,减少网络请求。
- 可扩展性:支持后续添加新功能,如性能监控。
技术实现
1. 项目初始化
使用 Objective-C 创建一个静态库。创建一个新项目,并设置基础文件结构:
1
2
3
4
5
6
7
8
9
TrackingSDK/
├── TrackingManager.h
├── TrackingManager.m
├── EventModel.h
├── EventModel.m
├── StorageManager.h
├── StorageManager.m
├── NetworkManager.h
└── NetworkManager.m
2. 事件模型
定义一个 EventModel
类来表示埋点事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// EventModel.h
@interface EventModel : NSObject
@property (nonatomic, copy) NSString *eventName;
@property (nonatomic, strong) NSDictionary *parameters;
@property (nonatomic, strong) NSDate *timestamp;
@end
// EventModel.m
@implementation EventModel
- (instancetype)initWithEventName:(NSString *)name parameters:(NSDictionary *)params {
self = [super init];
if (self) {
_eventName = [name copy];
_parameters = [params copy];
_timestamp = [NSDate date];
}
return self;
}
@end
3. 核心管理类
TrackingManager
是 SDK 的入口,提供事件记录和配置方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// TrackingManager.h
@interface TrackingManager : NSObject
+ (instancetype)sharedManager;
- (void)startWithApiKey:(NSString *)apiKey serverURL:(NSString *)url;
- (void)trackEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters;
@end
// TrackingManager.m
@implementation TrackingManager {
NSString *_apiKey;
NSString *_serverURL;
}
+ (instancetype)sharedManager {
static TrackingManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[TrackingManager alloc] init];
});
return instance;
}
- (void)startWithApiKey:(NSString *)apiKey serverURL:(NSString *)url {
_apiKey = apiKey;
_serverURL = url;
NSLog(@"Tracking SDK initialized with API Key: %@", apiKey);
}
- (void)trackEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters {
EventModel *event = [[EventModel alloc] initWithEventName:eventName parameters:parameters];
[self saveEvent:event];
[self uploadEventsIfNeeded];
}
- (void)saveEvent:(EventModel *)event {
// 交给 StorageManager 保存
[[StorageManager sharedManager] saveEvent:event];
}
- (void)uploadEventsIfNeeded {
// 交给 NetworkManager 上传
[[NetworkManager sharedManager] uploadEventsToServer:_serverURL withApiKey:_apiKey];
}
@end
4. 本地缓存
使用 NSUserDefaults
或文件存储来缓存事件数据。这里以文件为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// StorageManager.h
@interface StorageManager : NSObject
+ (instancetype)sharedManager;
- (void)saveEvent:(EventModel *)event;
- (NSArray<EventModel *> *)loadEvents;
- (void)clearEvents;
@end
// StorageManager.m
@implementation StorageManager
+ (instancetype)sharedManager { /* 单例实现 */ }
- (NSString *)eventsFilePath {
NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [documents stringByAppendingPathComponent:@"tracking_events.plist"];
}
- (void)saveEvent:(EventModel *)event {
NSMutableArray *events = [[self loadEvents] mutableCopy] ?: [NSMutableArray array];
[events addObject:[NSKeyedArchiver archivedDataWithRootObject:event]];
[events writeToFile:[self eventsFilePath] atomically:YES];
}
- (NSArray<EventModel *> *)loadEvents {
NSArray *dataArray = [NSArray arrayWithContentsOfFile:[self eventsFilePath]];
NSMutableArray *events = [NSMutableArray array];
for (NSData *data in dataArray) {
EventModel *event = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[events addObject:event];
}
return [events copy];
}
- (void)clearEvents {
[[NSFileManager defaultManager] removeItemAtPath:[self eventsFilePath] error:nil];
}
@end
5. 数据上传
使用 NSURLSession
实现批量上传:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// NetworkManager.m
@implementation NetworkManager
+ (instancetype)sharedManager { /* 单例实现 */ }
- (void)uploadEventsToServer:(NSString *)serverURL withApiKey:(NSString *)apiKey {
NSArray *events = [[StorageManager sharedManager] loadEvents];
if (events.count == 0) return;
NSMutableArray *jsonEvents = [NSMutableArray array];
for (EventModel *event in events) {
NSDictionary *dict = @{
@"event_name": event.eventName,
@"parameters": event.parameters ?: @{},
@"timestamp": @([event.timestamp timeIntervalSince1970])
};
[jsonEvents addObject:dict];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonEvents options:0 error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverURL]];
[request setHTTPMethod:@"POST"];
[request setValue:apiKey forHTTPHeaderField:@"X-API-Key"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:jsonData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error && [(NSHTTPURLResponse *)response statusCode] == 200) {
[[StorageManager sharedManager] clearEvents];
NSLog(@"Events uploaded successfully");
} else {
NSLog(@"Upload failed: %@", error.localizedDescription);
}
}];
[task resume];
}
@end
自动埋点:利用 Method Swizzling
手动埋点虽然灵活,但对于页面浏览或按钮点击这类常见事件,手动添加代码会增加开发负担。可以通过 Method Swizzling 实现自动埋点,拦截 UIViewController
的生命周期方法和 UIControl
的事件。
实现步骤
- 创建一个 Category: 在
TrackingManager
中添加自动埋点的逻辑,通过 Category 扩展UIViewController
和UIControl
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// UIViewController+Tracking.h
#import <UIKit/UIKit.h>
@interface UIViewController (Tracking)
@end
// UIViewController+Tracking.m
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewDidAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)swizzled_viewDidAppear:(BOOL)animated {
[self swizzled_viewDidAppear:animated]; // 调用原始方法
NSString *pageName = NSStringFromClass([self class]);
[[TrackingManager sharedManager] trackEvent:@"PageView" withParameters:@{@"page": pageName}];
}
@end
- 拦截 UIControl 事件: 类似地,扩展
UIControl
来捕获按钮点击:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UIControl+Tracking.m
@implementation UIControl (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_sendAction:to:forEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[self swizzled_sendAction:action to:target forEvent:event]; // 调用原始方法
if ([event type] == UIEventTypeTouches) {
NSString *actionName = NSStringFromSelector(action);
NSString *targetName = NSStringFromClass([target class]);
[[TrackingManager sharedManager] trackEvent:@"ButtonClick" withParameters:@{@"action": actionName, @"target": targetName}];
}
}
@end
使用示例
在 App 中集成 SDK:
1
2
3
4
5
6
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[TrackingManager sharedManager] startWithApiKey:@"your-api-key" serverURL:@"https://your-server.com/track"];
[[TrackingManager sharedManager] trackEvent:@"AppLaunch" withParameters:@{@"version": @"1.0.0"}];
return YES;
}
This post is licensed under CC BY 4.0 by the author.