[M] sync n3
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3aa65556739d487581dcbc33f001e23
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f4211ddaee63483da2cf08a3b9dbed6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TDAPPPushParams.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Charles on 6.5.23.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern id __td_get_userNotificationCenter(void);
|
||||
extern id __td_get_userNotificationCenter_delegate(void);
|
||||
extern NSDictionary * __td_get_userNotificationCenterResponse(id response);
|
||||
extern NSString * __td_get_userNotificationCenterRequestContentTitle(id response);
|
||||
extern NSString * __td_get_userNotificationCenterRequestContentBody(id response);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d606670f7a7c24e1d86b03c1fc6251ac
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// TDAPPPushParams.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Charles on 6.5.23.
|
||||
//
|
||||
|
||||
#import "TDAPPPushParams.h"
|
||||
|
||||
id __td_get_userNotificationCenter(void) {
|
||||
Class cls = NSClassFromString(@"UNUserNotificationCenter");
|
||||
SEL sel = NSSelectorFromString(@"currentNotificationCenter");
|
||||
if ([cls respondsToSelector:sel]) {
|
||||
id (*getUserNotificationCenterIMP)(id, SEL) = (NSString * (*)(id, SEL))[cls methodForSelector:sel];
|
||||
return getUserNotificationCenterIMP(cls, sel);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
id __td_get_userNotificationCenter_delegate(void) {
|
||||
Class cls = NSClassFromString(@"UNUserNotificationCenter");
|
||||
SEL sel = NSSelectorFromString(@"currentNotificationCenter");
|
||||
SEL delegateSel = NSSelectorFromString(@"delegate");
|
||||
if ([cls respondsToSelector:sel]) {
|
||||
id (*getUserNotificationCenterIMP)(id, SEL) = (id (*)(id, SEL))[cls methodForSelector:sel];
|
||||
id center = getUserNotificationCenterIMP(cls, sel);
|
||||
if (center) {
|
||||
id (*getUserNotificationCenterDelegateIMP)(id, SEL) = (id (*)(id, SEL))[center methodForSelector:delegateSel];
|
||||
id delegate = getUserNotificationCenterDelegateIMP(center, delegateSel);
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary * __td_get_userNotificationCenterResponse(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.userInfo"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * __td_get_userNotificationCenterRequestContentTitle(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.title"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * __td_get_userNotificationCenterRequestContentBody(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.body"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08ddbde4b0bb44a2781691f28d329404
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDAppDelegateProxyManager : NSObject
|
||||
|
||||
+ (instancetype)defaultManager;
|
||||
|
||||
- (void)proxyNotifications;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ad802e76f93c418c9419315b8a3d275
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
#import "TDAppDelegateProxyManager.h"
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import "TDNewSwizzle.h"
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import "TDMethodHelper.h"
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
#endif
|
||||
|
||||
@implementation TDAppDelegateProxyManager
|
||||
|
||||
+ (instancetype)defaultManager {
|
||||
static dispatch_once_t onceToken;
|
||||
static TDAppDelegateProxyManager *manager = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[TDAppDelegateProxyManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (void)proxyNotifications NS_EXTENSION_UNAVAILABLE_IOS(""){
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[TDMethodHelper swizzleRespondsToSelector];
|
||||
|
||||
[TDApplicationDelegateProxy resolveOptionalSelectorsForDelegate:[UIApplication sharedApplication].delegate];
|
||||
[TDApplicationDelegateProxy proxyDelegate:[UIApplication sharedApplication].delegate selectors:[NSSet setWithArray:@[@"application:didReceiveLocalNotification:",
|
||||
@"application:didReceiveRemoteNotification:fetchCompletionHandler:",
|
||||
@"application:handleOpenURL:",
|
||||
@"application:openURL:options:",
|
||||
@"application:continueUserActivity:restorationHandler:",
|
||||
@"application:performActionForShortcutItem:completionHandler:"]]];
|
||||
if (@available(iOS 10.0, *)) {
|
||||
if ([UNUserNotificationCenter currentNotificationCenter].delegate) {
|
||||
[TDUNUserNotificationCenterDelegateProxy proxyDelegate:[UNUserNotificationCenter currentNotificationCenter].delegate selectors:[NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]]];
|
||||
}
|
||||
NSError *error = NULL;
|
||||
[UNUserNotificationCenter td_new_swizzleMethod:@selector(setDelegate:) withMethod:@selector(thinkingdata_setDelegate:) error:&error];
|
||||
if (error) {
|
||||
NSLog(@"proxy notification delegate error: %@", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed11ab70d6d6943019d6d3995eeaadc2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDApplicationDelegateProxy : TDDelegateProxy
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47013b120f73c4885845dbb7dd840189
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import <objc/message.h>
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import "TDCommonUtil.h"
|
||||
|
||||
@implementation TDApplicationDelegateProxy
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, userInfo, completionHandler];
|
||||
[TDApplicationDelegateProxy trackEventWithTarget:self application:application remoteNotification:userInfo];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
||||
SEL selector = @selector(application:didReceiveLocalNotification:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, notification];
|
||||
[TDApplicationDelegateProxy trackEventWithTarget:self application:application localNotification:notification];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
|
||||
SEL selector = @selector(application:continueUserActivity:restorationHandler:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string: userActivity.webpageURL.absoluteString],@"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:application arg2:userActivity arg3:restorationHandler];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
SEL selector = @selector(application:openURL:options:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string:url.absoluteString],@"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:app arg2:url arg3:options];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
|
||||
SEL selector = @selector(application:handleOpenURL:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string:url.absoluteString], @"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:application arg2:url];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler{
|
||||
SEL selector = @selector(application:performActionForShortcutItem:completionHandler:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, shortcutItem, completionHandler];
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"",@"data": [TDCommonUtil dictionary:shortcutItem.userInfo]};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target application:(UIApplication *)application remoteNotification:(NSDictionary *)userInfo {
|
||||
|
||||
if (target != application.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
|
||||
NSLog(@"iOS version >= 10.0, callback for %@ was ignored.", @"application:didReceiveRemoteNotification:fetchCompletionHandler:");
|
||||
return;
|
||||
}
|
||||
|
||||
if (application.applicationState != UIApplicationStateInactive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:userInfo]};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target application:(UIApplication *)application localNotification:(UILocalNotification *)notification {
|
||||
|
||||
if (target != application.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isValidPushClick = NO;
|
||||
if (application.applicationState == UIApplicationStateInactive) {
|
||||
isValidPushClick = YES;
|
||||
}
|
||||
|
||||
if (!isValidPushClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
properties[@"alertBody"] = notification.alertBody;
|
||||
if (@available(iOS 8.2, *)) {
|
||||
properties[@"alertTitle"] = notification.alertTitle;
|
||||
}
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:properties]};
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSSet<NSString *> *)optionalSelectors {
|
||||
return [NSSet setWithArray:@[@"application:didReceiveLocalNotification:",
|
||||
@"application:didReceiveRemoteNotification:fetchCompletionHandler:",
|
||||
@"application:handleOpenURL:",
|
||||
@"application:openURL:options:",
|
||||
@"application:continueUserActivity:restorationHandler:",
|
||||
@"application:performActionForShortcutItem:completionHandler:"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2eec12b437e64f6886ebe51d6680b83
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDUNUserNotificationCenterDelegateProxy : TDDelegateProxy
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd9eda5b6572748ecaa3313c288c9e16
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/message.h>
|
||||
#import "TDAPPPushParams.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import "TDCommonUtil.h"
|
||||
|
||||
@implementation TDUNUserNotificationCenterDelegateProxy
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
|
||||
SEL selector = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
|
||||
[TDUNUserNotificationCenterDelegateProxy invokeWithTarget:self selector:selector, center, response, completionHandler];
|
||||
[TDUNUserNotificationCenterDelegateProxy trackEventWithTarget:self notificationCenter:center notificationResponse:response];
|
||||
}
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target notificationCenter:(UNUserNotificationCenter *)center notificationResponse:(UNNotificationResponse *)response API_AVAILABLE(ios(10.0)){
|
||||
|
||||
if (target != center.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary *userInfo = __td_get_userNotificationCenterResponse(response);
|
||||
if (userInfo) {
|
||||
[properties addEntriesFromDictionary:userInfo];
|
||||
}
|
||||
properties[@"title"] = __td_get_userNotificationCenterRequestContentTitle(response);
|
||||
properties[@"body"] = __td_get_userNotificationCenterRequestContentBody(response);
|
||||
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:properties]};
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableOpsReceiptProperties]) {
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
NSDictionary *userInfo = [response valueForKeyPath:@"notification.request.content.userInfo"];
|
||||
[TDAppLaunchReason td_ops_push_click:userInfo];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+ (NSSet<NSString *> *)optionalSelectors {
|
||||
return [NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8c262d70355c46f38a4fce70eb95cc5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIApplication (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id <UIApplicationDelegate>)delegate;
|
||||
@property (nonatomic, copy, nullable) NSDictionary *thinkingdata_launchOptions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b039553b9411c4e30b4fc767b7c2714d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static void *const kSALaunchOptions = (void *)&kSALaunchOptions;
|
||||
|
||||
@implementation UIApplication (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id<UIApplicationDelegate>)delegate {
|
||||
[TDApplicationDelegateProxy resolveOptionalSelectorsForDelegate:delegate];
|
||||
|
||||
[self thinkingdata_setDelegate:delegate];
|
||||
|
||||
if (!self.delegate) {
|
||||
return;
|
||||
}
|
||||
[TDApplicationDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"application:didReceiveLocalNotification:", @"application:didReceiveRemoteNotification:fetchCompletionHandler:"]]];
|
||||
}
|
||||
|
||||
- (NSDictionary *)thinkingdata_launchOptions {
|
||||
return objc_getAssociatedObject(self, kSALaunchOptions);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_launchOptions:(NSDictionary *)thinkingdata_launchOptions {
|
||||
objc_setAssociatedObject(self, kSALaunchOptions, thinkingdata_launchOptions, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17463cb6f209545bcbc20aad5129804d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UNUserNotificationCenter (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id <UNUserNotificationCenterDelegate>)delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd78c300cef5f4507a13b638ce74c1e5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
#import "UNUserNotificationCenter+TDPushClick.h"
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
|
||||
@implementation UNUserNotificationCenter (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id<UNUserNotificationCenterDelegate>)delegate {
|
||||
|
||||
[TDUNUserNotificationCenterDelegateProxy resolveOptionalSelectorsForDelegate:delegate];
|
||||
|
||||
[self thinkingdata_setDelegate:delegate];
|
||||
if (!self.delegate) {
|
||||
return;
|
||||
}
|
||||
[TDUNUserNotificationCenterDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73166a65f020c46e8a3c53cfa301c5f2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0aa41df3a429a4fd8915122407b7001a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TDDelegateProxyObject.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (TDDelegateProxy)
|
||||
|
||||
@property (nonatomic, copy, nullable) NSSet<NSString *> *thinkingdata_optionalSelectors;
|
||||
@property (nonatomic, strong, nullable) TDDelegateProxyObject *thinkingdata_delegateObject;
|
||||
|
||||
/// hook respondsToSelector to resolve optional selectors
|
||||
/// @param aSelector selector
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSProxy (TDDelegateProxy)
|
||||
|
||||
@property (nonatomic, copy, nullable) NSSet<NSString *> *thinkingdata_optionalSelectors;
|
||||
@property (nonatomic, strong, nullable) TDDelegateProxyObject *thinkingdata_delegateObject;
|
||||
|
||||
/// hook respondsToSelector to resolve optional selectors
|
||||
/// @param aSelector selector
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c165be5fc3eea4ab0bd9ff520104d3d8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static void *const kTDNSObjectDelegateOptionalSelectorsKey = (void *)&kTDNSObjectDelegateOptionalSelectorsKey;
|
||||
static void *const kTDNSObjectDelegateObjectKey = (void *)&kTDNSObjectDelegateObjectKey;
|
||||
|
||||
static void *const kTDNSProxyDelegateOptionalSelectorsKey = (void *)&kTDNSProxyDelegateOptionalSelectorsKey;
|
||||
static void *const kTDNSProxyDelegateObjectKey = (void *)&kTDNSProxyDelegateObjectKey;
|
||||
|
||||
@implementation NSObject (TDDelegateProxy)
|
||||
|
||||
- (NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
return objc_getAssociatedObject(self, kTDNSObjectDelegateOptionalSelectorsKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_optionalSelectors:(NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
objc_setAssociatedObject(self, kTDNSObjectDelegateOptionalSelectorsKey, thinkingdata_optionalSelectors, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
- (TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
return objc_getAssociatedObject(self, kTDNSObjectDelegateObjectKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_delegateObject:(TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
objc_setAssociatedObject(self, kTDNSObjectDelegateObjectKey, thinkingdata_delegateObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector {
|
||||
if ([self thinkingdata_respondsToSelector:aSelector]) {
|
||||
return YES;
|
||||
}
|
||||
if ([self.thinkingdata_optionalSelectors containsObject:NSStringFromSelector(aSelector)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSProxy (TDDelegateProxy)
|
||||
|
||||
- (NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
return objc_getAssociatedObject(self, kTDNSProxyDelegateOptionalSelectorsKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_optionalSelectors:(NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
objc_setAssociatedObject(self, kTDNSProxyDelegateOptionalSelectorsKey, thinkingdata_optionalSelectors, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
- (TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
return objc_getAssociatedObject(self, kTDNSProxyDelegateObjectKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_delegateObject:(TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
objc_setAssociatedObject(self, kTDNSProxyDelegateObjectKey, thinkingdata_delegateObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector {
|
||||
if ([self thinkingdata_respondsToSelector:aSelector]) {
|
||||
return YES;
|
||||
}
|
||||
if ([self.thinkingdata_optionalSelectors containsObject:NSStringFromSelector(aSelector)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 852dc5d94530d4aff8a96973c9986ad2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDClassHelper : NSObject
|
||||
|
||||
+ (Class _Nullable)allocateClassWithObject:(id)object className:(NSString *)className;
|
||||
|
||||
+ (void)registerClass:(Class)cla;
|
||||
|
||||
+ (BOOL)setObject:(id)object toClass:(Class)cla;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b05b7c406ad1f43d0bb4f1be994d8765
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
#import "TDClassHelper.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation TDClassHelper
|
||||
|
||||
+ (Class _Nullable)allocateClassWithObject:(id)object className:(NSString *)className {
|
||||
if (!object || className.length <= 0) {
|
||||
return nil;
|
||||
}
|
||||
Class originalClass = object_getClass(object);
|
||||
Class subclass = NSClassFromString(className);
|
||||
if (subclass) {
|
||||
return nil;
|
||||
}
|
||||
subclass = objc_allocateClassPair(originalClass, className.UTF8String, 0);
|
||||
if (class_getInstanceSize(originalClass) != class_getInstanceSize(subclass)) {
|
||||
return nil;
|
||||
}
|
||||
return subclass;
|
||||
}
|
||||
|
||||
+ (void)registerClass:(Class)cla {
|
||||
if (cla) {
|
||||
objc_registerClassPair(cla);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)setObject:(id)object toClass:(Class)cla {
|
||||
if (cla && object && object_getClass(object) != cla) {
|
||||
return object_setClass(object, cla);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aec2711162866493884cdef2cc925bb0
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TDHookDelegateProtocol <NSObject>
|
||||
@optional
|
||||
+ (NSSet<NSString *> *)optionalSelectors;
|
||||
|
||||
@end
|
||||
|
||||
@interface TDDelegateProxy : NSObject <TDHookDelegateProtocol>
|
||||
|
||||
+ (void)proxyDelegate:(id)delegate selectors:(NSSet<NSString *>*)selectors;
|
||||
|
||||
+ (void)invokeWithTarget:(NSObject *)target selector:(SEL)selector, ...;
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2;
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 arg3:(id)arg3;
|
||||
|
||||
+ (void)resolveOptionalSelectorsForDelegate:(id)delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d844d7614eae8439c904288502d39b52
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "TDMethodHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
static NSString * const kTDNSObjectRemoveObserverSelector = @"removeObserver:forKeyPath:";
|
||||
static NSString * const kTDNSObjectAddObserverSelector = @"addObserver:forKeyPath:options:context:";
|
||||
static NSString * const kTDNSObjectClassSelector = @"class";
|
||||
|
||||
@implementation TDDelegateProxy
|
||||
|
||||
+ (void)proxyDelegate:(id)delegate selectors:(NSSet<NSString *> *)selectors {
|
||||
if (object_isClass(delegate) || selectors.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class proxyClass = [self class];
|
||||
NSMutableSet *delegateSelectors = [NSMutableSet setWithSet:selectors];
|
||||
|
||||
TDDelegateProxyObject *object = [delegate thinkingdata_delegateObject];
|
||||
if (!object) {
|
||||
object = [[TDDelegateProxyObject alloc] initWithDelegate:delegate proxy:proxyClass];
|
||||
[delegate setThinkingdata_delegateObject:object];
|
||||
}
|
||||
|
||||
[delegateSelectors minusSet:object.selectors];
|
||||
if (delegateSelectors.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (object.thinkingClass) {
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.thinkingClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
|
||||
if (![object_getClass(delegate) isSubclassOfClass:object.thinkingClass]) {
|
||||
[TDClassHelper setObject:delegate toClass:object.thinkingClass];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (object.kvoClass) {
|
||||
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectRemoveObserverSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectRemoveObserverSelector];
|
||||
}
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.kvoClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
return;
|
||||
}
|
||||
|
||||
Class thinkingClass = [TDClassHelper allocateClassWithObject:delegate className:object.thinkingClassName];
|
||||
[TDClassHelper registerClass:thinkingClass];
|
||||
|
||||
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectAddObserverSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectAddObserverSelector];
|
||||
}
|
||||
|
||||
if (![object.selectors containsObject:kTDNSObjectClassSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectClassSelector];
|
||||
}
|
||||
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:thinkingClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
|
||||
[TDClassHelper setObject:delegate toClass:thinkingClass];
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithSelectors:(NSSet<NSString *> *)selectors fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
for (NSString *selector in selectors) {
|
||||
SEL sel = NSSelectorFromString(selector);
|
||||
[TDMethodHelper addInstanceMethodWithSelector:sel fromClass:fromClass toClass:toClass];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 arg3:(id)arg3 {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
BOOL returnValue = NO;
|
||||
@try {
|
||||
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2, arg3);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
BOOL returnValue = NO;
|
||||
@try {
|
||||
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
+ (void)invokeWithTarget:(NSObject *)target selector:(SEL)selector, ... {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
va_list args;
|
||||
va_start(args, selector);
|
||||
id arg1 = nil, arg2 = nil, arg3 = nil, arg4 = nil;
|
||||
NSInteger count = [NSStringFromSelector(selector) componentsSeparatedByString:@":"].count - 1;
|
||||
for (NSInteger i = 0; i < count; i++) {
|
||||
i == 0 ? (arg1 = va_arg(args, id)) : nil;
|
||||
i == 1 ? (arg2 = va_arg(args, id)) : nil;
|
||||
i == 2 ? (arg3 = va_arg(args, id)) : nil;
|
||||
i == 3 ? (arg4 = va_arg(args, id)) : nil;
|
||||
}
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
@try {
|
||||
void (*func)(struct objc_super *, SEL, id, id, id, id) = (void *)&objc_msgSendSuper;
|
||||
func(&targetSuper, selector, arg1, arg2, arg3, arg4);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void)resolveOptionalSelectorsForDelegate:(id)delegate {
|
||||
if (object_isClass(delegate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSSet *currentOptionalSelectors = ((NSObject *)delegate).thinkingdata_optionalSelectors;
|
||||
NSMutableSet *optionalSelectors = [[NSMutableSet alloc] init];
|
||||
if (currentOptionalSelectors) {
|
||||
[optionalSelectors unionSet:currentOptionalSelectors];
|
||||
}
|
||||
|
||||
if ([self respondsToSelector:@selector(optionalSelectors)] &&[self optionalSelectors]) {
|
||||
[optionalSelectors unionSet:[self optionalSelectors]];
|
||||
}
|
||||
((NSObject *)delegate).thinkingdata_optionalSelectors = [optionalSelectors copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Class
|
||||
@implementation TDDelegateProxy (Class)
|
||||
|
||||
- (Class)class {
|
||||
if (self.thinkingdata_delegateObject.delegateClass) {
|
||||
return self.thinkingdata_delegateObject.delegateClass;
|
||||
}
|
||||
return [super class];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - KVO
|
||||
@implementation TDDelegateProxy (KVO)
|
||||
|
||||
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
|
||||
[super addObserver:observer forKeyPath:keyPath options:options context:context];
|
||||
if (self.thinkingdata_delegateObject) {
|
||||
[TDMethodHelper replaceInstanceMethodWithDestinationSelector:@selector(class) sourceSelector:@selector(class) fromClass:TDDelegateProxy.class toClass:object_getClass(self)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
|
||||
BOOL oldClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
||||
[super removeObserver:observer forKeyPath:keyPath];
|
||||
BOOL newClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
||||
|
||||
if (oldClassIsKVO && !newClassIsKVO) {
|
||||
Class delegateProxy = self.thinkingdata_delegateObject.delegateProxy;
|
||||
NSSet *selectors = [self.thinkingdata_delegateObject.selectors copy];
|
||||
|
||||
[self.thinkingdata_delegateObject removeKVO];
|
||||
if ([delegateProxy respondsToSelector:@selector(proxyDelegate:selectors:)]) {
|
||||
[delegateProxy proxyDelegate:self selectors:selectors];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 814fa739d0a0447259f0fda304b44e4e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDDelegateProxyObject : NSObject
|
||||
|
||||
@property (nonatomic, strong) Class delegateISA;
|
||||
|
||||
@property (nonatomic, strong, nullable) Class kvoClass;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *thinkingClassName;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) Class thinkingClass;
|
||||
|
||||
@property (nonatomic, strong) id delegateClass;
|
||||
|
||||
@property (nonatomic, strong) Class delegateProxy;
|
||||
|
||||
@property (nonatomic, strong) NSMutableSet *selectors;
|
||||
|
||||
- (instancetype)initWithDelegate:(id)delegate proxy:(id)proxy;
|
||||
|
||||
- (void)removeKVO;
|
||||
|
||||
@end
|
||||
|
||||
@interface TDDelegateProxyObject (Utils)
|
||||
|
||||
+ (BOOL)isKVOClass:(Class _Nullable)cls;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b66965aa8d4f84c9fbcab5bb32c159a6
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
#import "TDDelegateProxyObject.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
NSString * const kTDDelegateClassThinkingSuffix = @"_TD.THINKINGDATA";
|
||||
NSString * const kTDDelegateClassKVOPrefix = @"KVONotifying_";
|
||||
|
||||
@implementation TDDelegateProxyObject
|
||||
|
||||
- (instancetype)initWithDelegate:(id)delegate proxy:(id)proxy {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_delegateProxy = proxy;
|
||||
|
||||
_selectors = [NSMutableSet set];
|
||||
_delegateClass = [delegate class];
|
||||
|
||||
Class cla = object_getClass(delegate);
|
||||
NSString *name = NSStringFromClass(cla);
|
||||
|
||||
if ([name containsString:kTDDelegateClassKVOPrefix]) {
|
||||
_delegateISA = class_getSuperclass(cla);
|
||||
_kvoClass = cla;
|
||||
} else if ([name containsString:kTDDelegateClassThinkingSuffix]) {
|
||||
_delegateISA = class_getSuperclass(cla);
|
||||
_thinkingClassName = name;
|
||||
} else {
|
||||
_delegateISA = cla;
|
||||
_thinkingClassName = [NSString stringWithFormat:@"%@%@", name, kTDDelegateClassThinkingSuffix];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (Class)thinkingClass {
|
||||
return NSClassFromString(self.thinkingClassName);
|
||||
}
|
||||
|
||||
- (void)removeKVO {
|
||||
self.kvoClass = nil;
|
||||
self.thinkingClassName = [NSString stringWithFormat:@"%@%@", self.delegateISA, kTDDelegateClassThinkingSuffix];
|
||||
[self.selectors removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Utils
|
||||
|
||||
@implementation TDDelegateProxyObject (Utils)
|
||||
|
||||
+ (BOOL)isKVOClass:(Class _Nullable)cls {
|
||||
return [NSStringFromClass(cls) containsString:kTDDelegateClassKVOPrefix];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6c49449459eb4a92bf83ab85a7be1e7
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDMethodHelper : NSObject
|
||||
|
||||
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass;
|
||||
|
||||
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (void)addClassMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (IMP _Nullable)replaceInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
/// swizzle respondsToSelector 方法
|
||||
/// 用于处理未实现代理方法也能采集事件的逻辑
|
||||
+ (void)swizzleRespondsToSelector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1cc9da14eecc4d23b345a15404edcab
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
#import "TDMethodHelper.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "TDNewSwizzle.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
|
||||
@implementation TDMethodHelper
|
||||
|
||||
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
|
||||
Method aMethod = class_getInstanceMethod(aClass, selector);
|
||||
return method_getImplementation(aMethod);
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
[self addInstanceMethodWithDestinationSelector:methodSelector sourceSelector:methodSelector fromClass:fromClass toClass:toClass];
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getInstanceMethod(fromClass, sourceSelector);
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
|
||||
IMP destinationIMP = [self implementationOfMethodSelector:destinationSelector fromClass:toClass];
|
||||
if (destinationIMP == methodIMP) {
|
||||
return;
|
||||
}
|
||||
class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)addClassMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getClassMethod(fromClass, sourceSelector);
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
|
||||
class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
}
|
||||
|
||||
+ (IMP _Nullable)replaceInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getInstanceMethod(fromClass, sourceSelector);
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
return class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
|
||||
+ (void)swizzleRespondsToSelector {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[NSObject td_new_swizzleMethod:@selector(respondsToSelector:)
|
||||
withMethod:@selector(thinkingdata_respondsToSelector:)
|
||||
error:NULL];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 009c284b5153443e8bff5c9ca6ba32e8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b294828e3aa214401ad6a0db7ebd8a63
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (TDNewSwizzle)
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
+ (BOOL)td_new_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withClass:(Class)altCla_ withMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2a9df7cedc0046aaad72584161c53f8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
#import "TDNewSwizzle.h"
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
#else
|
||||
#import <objc/objc-class.h>
|
||||
#endif
|
||||
|
||||
#define TDSetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
|
||||
if (ERROR_VAR) { \
|
||||
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
|
||||
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
|
||||
code:-1 \
|
||||
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
|
||||
}
|
||||
#define TDSetNSError(ERROR_VAR, FORMAT,...) TDSetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
|
||||
|
||||
#if OBJC_API_VERSION >= 2
|
||||
#define TDGetClass(obj) object_getClass(obj)
|
||||
#else
|
||||
#define TDGetClass(obj) (obj ? obj->isa : Nil)
|
||||
#endif
|
||||
|
||||
@implementation NSObject (TDNewSwizzle)
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withClass:(Class)altCla_ withMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
Method origMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!origMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
Method altMethod = class_getInstanceMethod(altCla_, altSel_);
|
||||
if (!altMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [altCla_ class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
class_addMethod(self,
|
||||
origSel_,
|
||||
class_getMethodImplementation(self, origSel_),
|
||||
method_getTypeEncoding(origMethod));
|
||||
class_addMethod(altCla_,
|
||||
altSel_,
|
||||
class_getMethodImplementation(altCla_, altSel_),
|
||||
method_getTypeEncoding(altMethod));
|
||||
|
||||
//交换之前,先对自定义方法进行添加
|
||||
BOOL didAddMethod = class_addMethod(self,
|
||||
altSel_,
|
||||
method_getImplementation(altMethod),
|
||||
method_getTypeEncoding(altMethod));
|
||||
if (didAddMethod) {
|
||||
method_exchangeImplementations(origMethod, class_getInstanceMethod(self, altSel_));
|
||||
}
|
||||
return didAddMethod;
|
||||
}
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
#if OBJC_API_VERSION >= 2
|
||||
Method origMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!origMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
Method altMethod = class_getInstanceMethod(self, altSel_);
|
||||
if (!altMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
class_addMethod(self,
|
||||
origSel_,
|
||||
class_getMethodImplementation(self, origSel_),
|
||||
method_getTypeEncoding(origMethod));
|
||||
class_addMethod(self,
|
||||
altSel_,
|
||||
class_getMethodImplementation(self, altSel_),
|
||||
method_getTypeEncoding(altMethod));
|
||||
|
||||
method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
|
||||
return YES;
|
||||
#else
|
||||
// Scan for non-inherited methods.
|
||||
Method directOriginalMethod = NULL, directAlternateMethod = NULL;
|
||||
|
||||
void *iterator = NULL;
|
||||
struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
|
||||
while (mlist) {
|
||||
int method_index = 0;
|
||||
for (; method_index < mlist->method_count; method_index++) {
|
||||
if (mlist->method_list[method_index].method_name == origSel_) {
|
||||
assert(!directOriginalMethod);
|
||||
directOriginalMethod = &mlist->method_list[method_index];
|
||||
}
|
||||
if (mlist->method_list[method_index].method_name == altSel_) {
|
||||
assert(!directAlternateMethod);
|
||||
directAlternateMethod = &mlist->method_list[method_index];
|
||||
}
|
||||
}
|
||||
mlist = class_nextMethodList(self, &iterator);
|
||||
}
|
||||
|
||||
// If either method is inherited, copy it up to the target class to make it non-inherited.
|
||||
if (!directOriginalMethod || !directAlternateMethod) {
|
||||
Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
|
||||
if (!directOriginalMethod) {
|
||||
inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!inheritedOriginalMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
if (!directAlternateMethod) {
|
||||
inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
|
||||
if (!inheritedAlternateMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
|
||||
struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
|
||||
hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
|
||||
hoisted_method_list->method_count = hoisted_method_count;
|
||||
Method hoisted_method = hoisted_method_list->method_list;
|
||||
|
||||
if (!directOriginalMethod) {
|
||||
bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
|
||||
directOriginalMethod = hoisted_method++;
|
||||
}
|
||||
if (!directAlternateMethod) {
|
||||
bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
|
||||
directAlternateMethod = hoisted_method;
|
||||
}
|
||||
class_addMethods(self, hoisted_method_list);
|
||||
}
|
||||
|
||||
// Swizzle.
|
||||
IMP temp = directOriginalMethod->method_imp;
|
||||
directOriginalMethod->method_imp = directAlternateMethod->method_imp;
|
||||
directAlternateMethod->method_imp = temp;
|
||||
|
||||
return YES;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (BOOL)td_new_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
return [TDGetClass((id)self) td_new_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8ab506fca17c4ec1815a15a2b7eb5f7
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TDAppLaunchReason.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/11/17.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSDictionary *appPushClickDic;
|
||||
|
||||
@interface TDAppLaunchReason : NSObject
|
||||
|
||||
@property(nonatomic, copy) NSDictionary *appLaunchParams;
|
||||
|
||||
+ (TDAppLaunchReason *)sharedInstance;
|
||||
|
||||
- (void)clearAppLaunchParams;
|
||||
|
||||
+ (void)td_ops_push_click:(NSDictionary *)userInfo;
|
||||
|
||||
+ (NSDictionary*)getAppPushDic;
|
||||
|
||||
+ (void)clearAppPushParams;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f37b994396cdc4d7780bcb516fcc6d51
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// TDAppLaunchReason.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/11/17.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "TDCommonUtil.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppState.h"
|
||||
#import "ThinkingAnalyticsSDKPrivate.h"
|
||||
#import "TDAppDelegateProxyManager.h"
|
||||
#import "TAPushClickEvent.h"
|
||||
|
||||
@implementation TDAppLaunchReason
|
||||
|
||||
|
||||
+ (void)load {
|
||||
|
||||
[TDPresetProperties disPresetProperties];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:[TDAppLaunchReason sharedInstance] selector:@selector(_applicationDidFinishLaunchingNotification:) name:UIApplicationDidFinishLaunchingNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:[TDAppLaunchReason sharedInstance] selector:@selector(_applicationDidEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
}
|
||||
|
||||
+ (void)td_ops_push_click:(NSDictionary *)userInfo {
|
||||
|
||||
@try {
|
||||
if ([userInfo.allKeys containsObject:@"te_extras"] && [userInfo[@"te_extras"] isKindOfClass:[NSString class]]) {
|
||||
NSData *jsonData = [userInfo[@"te_extras"] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *err;
|
||||
NSDictionary *responseDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
NSDictionary *opsReceiptProperties = responseDic[@"#ops_receipt_properties"];
|
||||
if ([opsReceiptProperties isKindOfClass:[NSString class]]) {
|
||||
NSString *opsStr = (NSString *)opsReceiptProperties;
|
||||
opsReceiptProperties = [NSJSONSerialization JSONObjectWithData:[opsStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&err];
|
||||
}
|
||||
if (opsReceiptProperties && [opsReceiptProperties isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary *dic = [ThinkingAnalyticsSDK _getAllInstances];
|
||||
if(dic == nil || dic.count == 0){
|
||||
appPushClickDic = opsReceiptProperties;
|
||||
}else{
|
||||
for (NSString *instanceToken in dic.allKeys) {
|
||||
ThinkingAnalyticsSDK *instance = dic[instanceToken];
|
||||
TAPushClickEvent *pushEvent = [[TAPushClickEvent alloc]initWithName: @"ops_push_click"];
|
||||
pushEvent.ops = opsReceiptProperties;
|
||||
[instance autoTrackWithEvent:pushEvent properties:@{}];
|
||||
[instance flush];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSDictionary *)getAppPushDic{
|
||||
return appPushClickDic;
|
||||
}
|
||||
|
||||
+ (void)clearAppPushParams{
|
||||
appPushClickDic = nil;
|
||||
}
|
||||
|
||||
+ (TDAppLaunchReason *)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TDAppLaunchReason *appLaunchManager;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
appLaunchManager = [TDAppLaunchReason new];
|
||||
});
|
||||
return appLaunchManager;
|
||||
}
|
||||
|
||||
- (void)clearAppLaunchParams {
|
||||
self.appLaunchParams = @{@"url":@"",
|
||||
@"data":@{}};
|
||||
}
|
||||
|
||||
- (void)_applicationDidEnterBackgroundNotification:(NSNotification *)notification {
|
||||
[self clearAppLaunchParams];
|
||||
}
|
||||
|
||||
// 拦截冷启动和热启动的参数
|
||||
- (void)_applicationDidFinishLaunchingNotification:(NSNotification *)notification {
|
||||
|
||||
__weak TDAppLaunchReason *weakSelf = self;
|
||||
|
||||
NSDictionary *launchOptions = notification.userInfo;
|
||||
NSString *url = [self getInitDeeplink:launchOptions];
|
||||
NSDictionary *data = [self getInitData:launchOptions];
|
||||
|
||||
// 发送推送事件
|
||||
if (![TDPresetProperties disableOpsReceiptProperties] && launchOptions) {
|
||||
NSDictionary *remoteNotification = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
|
||||
[TDAppLaunchReason td_ops_push_click:remoteNotification];
|
||||
}
|
||||
|
||||
// 记录冷启动启动原因
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
|
||||
if (!launchOptions) {
|
||||
[weakSelf clearAppLaunchParams];
|
||||
} else if ([url isKindOfClass:[NSString class]] && url.length) {
|
||||
self.appLaunchParams = @{@"url": [TDCommonUtil string:url],
|
||||
@"data": @{}};
|
||||
} else {
|
||||
self.appLaunchParams = @{@"url": @"",
|
||||
@"data": [TDCommonUtil dictionary:data]};
|
||||
}
|
||||
}
|
||||
|
||||
UIApplication *application = [TDAppState sharedApplication];
|
||||
id applicationDelegate = [application delegate];
|
||||
if (applicationDelegate == nil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppDelegateProxyManager defaultManager] proxyNotifications];
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableOpsReceiptProperties]) {
|
||||
[[TDAppDelegateProxyManager defaultManager] proxyNotifications];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getInitDeeplink:(NSDictionary *)launchOptions {
|
||||
|
||||
if (!launchOptions || ![launchOptions isKindOfClass:[NSDictionary class]]) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
if ([launchOptions isKindOfClass:[NSDictionary class]] &&
|
||||
[launchOptions.allKeys containsObject:UIApplicationLaunchOptionsURLKey]) {
|
||||
|
||||
return launchOptions[UIApplicationLaunchOptionsURLKey];
|
||||
|
||||
} else if ([launchOptions isKindOfClass:[NSDictionary class]] &&
|
||||
[launchOptions.allKeys containsObject:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
|
||||
|
||||
NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
|
||||
NSString *type = userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey];
|
||||
if ([type isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
NSUserActivity *userActivity = userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"];
|
||||
return userActivity.webpageURL.absoluteString;
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSDictionary *)getInitData:(NSDictionary *)launchOptions {
|
||||
|
||||
if (!launchOptions || ![launchOptions isKindOfClass:[NSDictionary class]]) {
|
||||
return @{};
|
||||
}
|
||||
|
||||
if ([launchOptions.allKeys containsObject:UIApplicationLaunchOptionsLocalNotificationKey]) {
|
||||
// 本地推送可能会解析不出alertbody,这里特殊处理一下
|
||||
UILocalNotification *notification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
properties[@"alertBody"] = notification.alertBody;
|
||||
if (@available(iOS 8.2, *)) {
|
||||
properties[@"alertTitle"] = notification.alertTitle;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
return launchOptions;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2153a11937bd248f793246e3f848aaab
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20131090d388c4844a66a22ba19bcf58
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// TAAppLifeCycle.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/28.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// APP life cycle
|
||||
typedef NS_ENUM(NSUInteger, TAAppLifeCycleState) {
|
||||
TAAppLifeCycleStateInit = 1, // init status
|
||||
TAAppLifeCycleStateBackgroundStart,
|
||||
TAAppLifeCycleStateStart,
|
||||
TAAppLifeCycleStateEnd,
|
||||
TAAppLifeCycleStateTerminate,
|
||||
};
|
||||
|
||||
/// When the life cycle status is about to change, this notification will be sent
|
||||
/// object: The object is the current life cycle object
|
||||
/// userInfo: Contains two keys kTAAppLifeCycleNewStateKey and kTAAppLifeCycleOldStateKey
|
||||
extern NSNotificationName const kTAAppLifeCycleStateWillChangeNotification;
|
||||
|
||||
/// When the life cycle status changes, this notification will be sent
|
||||
/// object: The object is the current lifecycle object
|
||||
/// userInfo: Contains two keys kTAAppLifeCycleNewStateKey and kTAAppLifeCycleOldStateKey
|
||||
extern NSNotificationName const kTAAppLifeCycleStateDidChangeNotification;
|
||||
|
||||
/// In the status change notification, get the new status
|
||||
extern NSString * const kTAAppLifeCycleNewStateKey;
|
||||
|
||||
/// In the status change notification, get the status before the change
|
||||
extern NSString * const kTAAppLifeCycleOldStateKey;
|
||||
|
||||
@interface TAAppLifeCycle : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) TAAppLifeCycleState state;
|
||||
|
||||
+ (void)startMonitor;
|
||||
|
||||
+ (instancetype)shareInstance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 885238a5636ac43b7a9be0bfdd0537b2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// TAAppLifeCycle.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/28.
|
||||
//
|
||||
|
||||
#import "TAAppLifeCycle.h"
|
||||
#import "TDAppState.h"
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#if __has_include(<ThinkingSDK/TDLogging.h>)
|
||||
#import <ThinkingSDK/TDLogging.h>
|
||||
#else
|
||||
#import "TDLogging.h"
|
||||
#endif
|
||||
|
||||
NSNotificationName const kTAAppLifeCycleStateWillChangeNotification = @"cn.thinkingdata.TAAppLifeCycleStateWillChange";
|
||||
NSNotificationName const kTAAppLifeCycleStateDidChangeNotification = @"cn.thinkingdata.TAAppLifeCycleStateDidChange";
|
||||
NSString * const kTAAppLifeCycleNewStateKey = @"new";
|
||||
NSString * const kTAAppLifeCycleOldStateKey = @"old";
|
||||
|
||||
|
||||
@interface TAAppLifeCycle ()
|
||||
/// status
|
||||
@property (nonatomic, assign) TAAppLifeCycleState state;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TAAppLifeCycle
|
||||
|
||||
+ (void)startMonitor {
|
||||
[TAAppLifeCycle shareInstance];
|
||||
}
|
||||
|
||||
+ (instancetype)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TAAppLifeCycle *appLifeCycle = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
appLifeCycle = [[TAAppLifeCycle alloc] init];
|
||||
});
|
||||
return appLifeCycle;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_state = TAAppLifeCycleStateInit;
|
||||
[self registerListeners];
|
||||
[self setupLaunchedState];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)registerListeners {
|
||||
if ([TDAppState runningInAppExtension]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
#if TARGET_OS_IOS
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(applicationDidBecomeActive:)
|
||||
name:UIApplicationDidBecomeActiveNotification
|
||||
object:nil];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(applicationDidEnterBackground:)
|
||||
name:UIApplicationDidEnterBackgroundNotification
|
||||
object:nil];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(applicationWillTerminate:)
|
||||
name:UIApplicationWillTerminateNotification
|
||||
object:nil];
|
||||
|
||||
#elif TARGET_OS_OSX
|
||||
|
||||
// [notificationCenter addObserver:self
|
||||
// selector:@selector(applicationDidFinishLaunching:)
|
||||
// name:NSApplicationDidFinishLaunchingNotification
|
||||
// object:nil];
|
||||
//
|
||||
// [notificationCenter addObserver:self
|
||||
// selector:@selector(applicationDidBecomeActive:)
|
||||
// name:NSApplicationDidBecomeActiveNotification
|
||||
// object:nil];
|
||||
// [notificationCenter addObserver:self
|
||||
// selector:@selector(applicationDidResignActive:)
|
||||
// name:NSApplicationDidResignActiveNotification
|
||||
// object:nil];
|
||||
//
|
||||
// [notificationCenter addObserver:self
|
||||
// selector:@selector(applicationWillTerminate:)
|
||||
// name:NSApplicationWillTerminateNotification
|
||||
// object:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)setupLaunchedState {
|
||||
if ([TDAppState runningInAppExtension]) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_block_t mainThreadBlock = ^(){
|
||||
#if TARGET_OS_IOS
|
||||
UIApplication *application = [TDAppState sharedApplication];
|
||||
BOOL isAppStateBackground = application.applicationState == UIApplicationStateBackground;
|
||||
#else
|
||||
BOOL isAppStateBackground = NO;
|
||||
#endif
|
||||
[TDAppState shareInstance].relaunchInBackground = isAppStateBackground;
|
||||
|
||||
self.state = isAppStateBackground ? TAAppLifeCycleStateBackgroundStart : TAAppLifeCycleStateStart;
|
||||
};
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// The reason why iOS 13 and above modify the status in the block of the asynchronous main queue:+
|
||||
// 1. Make sure that the initialization of the SDK has been completed before sending the appstatus change notification. This can ensure that the public properties have been set when the automatic collection management class sends the app_start event (in fact, it can also be achieved by listening to UIApplicationDidFinishLaunchingNotification)
|
||||
// 2. In a project that contains SceneDelegate, it is accurate to delay obtaining applicationState (obtaining by listening to UIApplicationDidFinishLaunchingNotification is inaccurate)
|
||||
dispatch_async(dispatch_get_main_queue(), mainThreadBlock);
|
||||
} else {
|
||||
// iOS 13 and below handle background wakeup and cold start (non-delayed initialization) by listening to the notification of UIApplicationDidFinishLaunchingNotification:
|
||||
// 1. When iOS 13 or later wakes up in the background, the block of the asynchronous main queue will not be executed. So you need to listen to UIApplicationDidFinishLaunchingNotification at the same time
|
||||
// 2. iOS 13 and below will not contain SceneDelegate
|
||||
#if TARGET_OS_IOS
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(applicationDidFinishLaunching:)
|
||||
name:UIApplicationDidFinishLaunchingNotification
|
||||
object:nil];
|
||||
#endif
|
||||
// Handle cold start below iOS 13, where the client delays initialization. UIApplicationDidFinishLaunchingNotification notification has been missed when lazy initialization
|
||||
dispatch_async(dispatch_get_main_queue(), mainThreadBlock);
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Notification Action
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
#if TARGET_OS_IOS
|
||||
UIApplication *application = [TDAppState sharedApplication];
|
||||
BOOL isAppStateBackground = application.applicationState == UIApplicationStateBackground;
|
||||
#else
|
||||
BOOL isAppStateBackground = NO;
|
||||
#endif
|
||||
|
||||
[TDAppState shareInstance].relaunchInBackground = isAppStateBackground;
|
||||
|
||||
self.state = isAppStateBackground ? TAAppLifeCycleStateBackgroundStart : TAAppLifeCycleStateStart;
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
TDLogDebug(@"application did become active");
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
if (![notification.object isKindOfClass:[UIApplication class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIApplication *application = (UIApplication *)notification.object;
|
||||
if (application.applicationState != UIApplicationStateActive) {
|
||||
return;
|
||||
}
|
||||
#elif TARGET_OS_OSX
|
||||
if (![notification.object isKindOfClass:[NSApplication class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSApplication *application = (NSApplication *)notification.object;
|
||||
if (!application.isActive) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
[TDAppState shareInstance].relaunchInBackground = NO;
|
||||
|
||||
self.state = TAAppLifeCycleStateStart;
|
||||
}
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
- (void)applicationDidEnterBackground:(NSNotification *)notification {
|
||||
TDLogDebug(@"application did enter background");
|
||||
|
||||
if (![notification.object isKindOfClass:[UIApplication class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIApplication *application = (UIApplication *)notification.object;
|
||||
if (application.applicationState != UIApplicationStateBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.state = TAAppLifeCycleStateEnd;
|
||||
}
|
||||
|
||||
#elif TARGET_OS_OSX
|
||||
- (void)applicationDidResignActive:(NSNotification *)notification {
|
||||
TDLogDebug(@"application did resignActive");
|
||||
|
||||
if (![notification.object isKindOfClass:[NSApplication class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSApplication *application = (NSApplication *)notification.object;
|
||||
if (application.isActive) {
|
||||
return;
|
||||
}
|
||||
self.state = TAAppLifeCycleStateEnd;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)notification {
|
||||
TDLogDebug(@"application will terminate");
|
||||
|
||||
self.state = TAAppLifeCycleStateTerminate;
|
||||
}
|
||||
|
||||
//MARK: - Setter
|
||||
|
||||
- (void)setState:(TAAppLifeCycleState)state {
|
||||
|
||||
if (_state == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
[TDAppState shareInstance].isActive = (state == TAAppLifeCycleStateStart);
|
||||
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||
userInfo[kTAAppLifeCycleNewStateKey] = @(state);
|
||||
userInfo[kTAAppLifeCycleOldStateKey] = @(_state);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kTAAppLifeCycleStateWillChangeNotification object:self userInfo:userInfo];
|
||||
|
||||
_state = state;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kTAAppLifeCycleStateDidChangeNotification object:self userInfo:userInfo];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90a768aa97d9f4a67a459208b6dc419f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// TDAppState.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/9/24.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDAppState : NSObject
|
||||
/// Whether to start in the background. When the app is woken up by silently pushing the background, or when the location change wakes up the app, value = YES. (thread safe)
|
||||
@property (atomic, assign) BOOL relaunchInBackground;
|
||||
|
||||
/// Whether the current app is in the foreground
|
||||
@property (atomic, assign) BOOL isActive;
|
||||
|
||||
+ (instancetype)shareInstance;
|
||||
|
||||
+ (id)sharedApplication;
|
||||
|
||||
+ (BOOL)runningInAppExtension;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfe06ac7bb20d44f4b38427283e52402
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// TDAppState.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/9/24.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TDAppState.h"
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
NSString *_td_lastKnownState;
|
||||
|
||||
@implementation TDAppState
|
||||
|
||||
+ (instancetype)shareInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TDAppState *appState;
|
||||
dispatch_once(&onceToken, ^{
|
||||
appState = [TDAppState new];
|
||||
});
|
||||
return appState;
|
||||
}
|
||||
|
||||
+ (id)sharedApplication {
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
if ([self runningInAppExtension]) {
|
||||
return nil;
|
||||
}
|
||||
return [[UIApplication class] performSelector:@selector(sharedApplication)];
|
||||
|
||||
#endif
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (BOOL)runningInAppExtension {
|
||||
#if TARGET_OS_IOS
|
||||
return [[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"];
|
||||
#endif
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 439283b9eaf2e4d26883e97a319af143
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1b5729d7c0b24bddbf39df25636523b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60e81b49c2ab941b48f1746f0b7f409d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TAAppEndEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/17.
|
||||
//
|
||||
|
||||
#import "TAAutoTrackEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAAppEndEvent : TAAutoTrackEvent
|
||||
@property (nonatomic, copy) NSString *screenName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab91a24abdc494f7b8c0862a2b45cce3
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// TAAppEndEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/17.
|
||||
//
|
||||
|
||||
#import "TAAppEndEvent.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
@implementation TAAppEndEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
|
||||
if (![TDPresetProperties disableScreenName]) {
|
||||
self.properties[@"#screen_name"] = self.screenName ?: @"";
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 294d54bcf09bf453c8bf7febc52d1dcb
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TAAppStartEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/17.
|
||||
//
|
||||
|
||||
#import "TAAutoTrackEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAAppStartEvent : TAAutoTrackEvent
|
||||
@property (nonatomic, copy) NSString *startReason;
|
||||
@property (nonatomic, assign) BOOL resumeFromBackground;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77af5449a173c4d05aa967435187b647
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// TAAppStartEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/17.
|
||||
//
|
||||
|
||||
#import "TAAppStartEvent.h"
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
@implementation TAAppStartEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
|
||||
if (![TDPresetProperties disableResumeFromBackground]) {
|
||||
self.properties[@"#resume_from_background"] = @(self.resumeFromBackground);
|
||||
}
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
self.properties[@"#start_reason"] = self.startReason;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1294c9d002c2c4f879fcc49269cbc7e9
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TAAutoClickEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TAAutoTrackEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAAutoClickEvent : TAAutoTrackEvent
|
||||
@property (nonatomic, copy) NSString *elementId;
|
||||
@property (nonatomic, copy) NSString *elementContent;
|
||||
@property (nonatomic, copy) NSString *elementType;
|
||||
@property (nonatomic, copy) NSString *elementPosition;
|
||||
@property (nonatomic, copy) NSString *pageTitle;
|
||||
@property (nonatomic, copy) NSString *screenName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0b6e9b4fa84e40ffa4799dd7aed576d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// TAAutoClickEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TAAutoClickEvent.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
@implementation TAAutoClickEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
|
||||
if (![TDPresetProperties disableScreenName]) {
|
||||
self.properties[@"#screen_name"] = self.screenName;
|
||||
}
|
||||
if (![TDPresetProperties disableElementId]) {
|
||||
self.properties[@"#element_id"] = self.elementId;
|
||||
}
|
||||
if (![TDPresetProperties disableElementType]) {
|
||||
self.properties[@"#element_type"] = self.elementType;
|
||||
}
|
||||
if (![TDPresetProperties disableElementContent]) {
|
||||
self.properties[@"#element_content"] = self.elementContent;
|
||||
}
|
||||
if (![TDPresetProperties disableElementPosition]) {
|
||||
self.properties[@"#element_position"] = self.elementPosition;
|
||||
}
|
||||
if (![TDPresetProperties disableTitle]) {
|
||||
self.properties[@"#title"] = self.pageTitle;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22c8f205dbc9a492db570c08357cab4f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// TAAutoPageViewEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TAAutoTrackEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAAutoPageViewEvent : TAAutoTrackEvent
|
||||
@property (nonatomic, copy) NSString *pageUrl;
|
||||
@property (nonatomic, copy) NSString *referrer;
|
||||
@property (nonatomic, copy) NSString *pageTitle;
|
||||
@property (nonatomic, copy) NSString *screenName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfeb4b3d07e4d4c0e89b139b8d7e4184
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TAAutoPageViewEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TAAutoPageViewEvent.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
@implementation TAAutoPageViewEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
|
||||
if (![TDPresetProperties disableScreenName]) {
|
||||
self.properties[@"#screen_name"] = self.screenName;
|
||||
}
|
||||
if (![TDPresetProperties disableTitle]) {
|
||||
self.properties[@"#title"] = self.pageTitle;
|
||||
}
|
||||
if (![TDPresetProperties disableUrl]) {
|
||||
self.properties[@"#url"] = self.pageUrl;
|
||||
}
|
||||
if (![TDPresetProperties disableReferrer]) {
|
||||
self.properties[@"#referrer"] = self.referrer;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c0bed1add07542e6bcf1fda661ce0d5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// TAAutoTrackEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TATrackEvent.h"
|
||||
#import "TDConstant.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAAutoTrackEvent : TATrackEvent
|
||||
|
||||
/// It is used to record the dynamic public properties of automatic collection events. The dynamic public properties need to be obtained in the current thread where the event occurs
|
||||
@property (nonatomic, strong) NSDictionary *autoDynamicSuperProperties;
|
||||
|
||||
/// Static public property for logging autocollection events
|
||||
@property (nonatomic, strong) NSDictionary *autoSuperProperties;
|
||||
|
||||
/// Returns the automatic collection type
|
||||
- (ThinkingAnalyticsAutoTrackEventType)autoTrackEventType;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: debe332a658c34f4a97564ba712921a6
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// TAAutoTrackEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/6/15.
|
||||
//
|
||||
|
||||
#import "TAAutoTrackEvent.h"
|
||||
#import "ThinkingAnalyticsSDKPrivate.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
@implementation TAAutoTrackEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
// Reprocess the duration of automatic collection events, mainly app_start, app_end
|
||||
// app_start app_end events are collected by the automatic collection management class. There are the following problems: the automatic collection management class and the timeTracker event duration management class are processed by listening to appLifeCycle notifications, so they are not at a precise and unified time point. There will be small errors that need to be eliminated.
|
||||
// After testing, the error is less than 0.01s.
|
||||
CGFloat minDuration = 0.01;
|
||||
if (![TDPresetProperties disableDuration]) {
|
||||
if (self.foregroundDuration > minDuration) {
|
||||
self.properties[@"#duration"] = @([NSString stringWithFormat:@"%.3f", self.foregroundDuration].floatValue);
|
||||
}
|
||||
}
|
||||
if (![TDPresetProperties disableBackgroundDuration]) {
|
||||
if (self.backgroundDuration > minDuration) {
|
||||
self.properties[@"#background_duration"] = @([NSString stringWithFormat:@"%.3f", self.backgroundDuration].floatValue);
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
- (ThinkingAnalyticsAutoTrackEventType)autoTrackEventType {
|
||||
if ([self.eventName isEqualToString:TD_APP_START_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppStart;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_START_BACKGROUND_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppStart;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_END_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppEnd;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_VIEW_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppViewScreen;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_CLICK_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppClick;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_CRASH_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppViewCrash;
|
||||
} else if ([self.eventName isEqualToString:TD_APP_INSTALL_EVENT]) {
|
||||
return ThinkingAnalyticsEventTypeAppInstall;
|
||||
} else {
|
||||
return ThinkingAnalyticsEventTypeNone;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ta_validateKey:(NSString *)key value:(id)value error:(NSError *__autoreleasing _Nullable *)error {
|
||||
[TAPropertyValidator validateAutoTrackEventPropertyKey:key value:value error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 457a15183a12f43288ad47022c24f21d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TAPushClickEvent.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by liulongbing on 2023/5/31.
|
||||
//
|
||||
#import "TAAutoTrackEvent.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TAPushClickEvent : TAAutoTrackEvent
|
||||
|
||||
@property (nonatomic, strong) NSDictionary *ops;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5da924cd82c7e49b6a2d9eafec038882
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TAPushClickEvent.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by liulongbing on 2023/5/31.
|
||||
//
|
||||
|
||||
#import "TAPushClickEvent.h"
|
||||
|
||||
@implementation TAPushClickEvent
|
||||
|
||||
- (NSMutableDictionary *)jsonObject {
|
||||
NSMutableDictionary *dict = [super jsonObject];
|
||||
self.properties[@"#ops_receipt_properties"] = self.ops;
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ccea408f557438a9f8be087342eab3
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// TAAutoTrackProtocol.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/7/1.
|
||||
//
|
||||
|
||||
#ifndef TAAutoTrackProtocol_h
|
||||
#define TAAutoTrackProtocol_h
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol TDUIViewAutoTrackDelegate
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
UITableView event properties
|
||||
|
||||
@return event properties
|
||||
*/
|
||||
- (NSDictionary *)thinkingAnalytics_tableView:(UITableView *)tableView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
APPID UITableView event properties
|
||||
|
||||
@return event properties
|
||||
*/
|
||||
- (NSDictionary *)thinkingAnalyticsWithAppid_tableView:(UITableView *)tableView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
UICollectionView event properties
|
||||
|
||||
@return event properties
|
||||
*/
|
||||
- (NSDictionary *)thinkingAnalytics_collectionView:(UICollectionView *)collectionView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
APPID UICollectionView event properties
|
||||
|
||||
@return event properties
|
||||
*/
|
||||
- (NSDictionary *)thinkingAnalyticsWithAppid_collectionView:(UICollectionView *)collectionView autoTrackPropertiesAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol TDAutoTracker
|
||||
|
||||
@optional
|
||||
|
||||
- (NSDictionary *)getTrackProperties;
|
||||
|
||||
|
||||
- (NSDictionary *)getTrackPropertiesWithAppid;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
Automatically track the page
|
||||
*/
|
||||
@protocol TDScreenAutoTracker <TDAutoTracker>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
Attributes for custom page view events
|
||||
*/
|
||||
- (NSString *)getScreenUrl;
|
||||
|
||||
/**
|
||||
Configure the properties of the APPID custom page view event
|
||||
*/
|
||||
- (NSDictionary *)getScreenUrlWithAppid;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* TAAutoTrackProtocol_h */
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18165eaf66907443c93552539b274cd0
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// TAAutoTrackPublicHeader.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Yangxiongon 2022/7/1.
|
||||
//
|
||||
|
||||
#ifndef TAAutoTrackPublicHeader_h
|
||||
#define TAAutoTrackPublicHeader_h
|
||||
|
||||
#if __has_include(<ThinkingSDK/TAAutoTrackProtocol.h>)
|
||||
#import <ThinkingSDK/TAAutoTrackProtocol.h>
|
||||
#else
|
||||
#import "TAAutoTrackProtocol.h"
|
||||
#endif
|
||||
|
||||
#if __has_include(<ThinkingSDK/UIView+ThinkingAnalytics.h>)
|
||||
#import <ThinkingSDK/UIView+ThinkingAnalytics.h>
|
||||
#else
|
||||
#import "UIView+ThinkingAnalytics.h"
|
||||
#endif
|
||||
|
||||
#endif /* TAAutoTrackPublicHeader_h */
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0346fe03091074a4ab4c1763dc7d6d5f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
#import "ThinkingAnalyticsSDK.h"
|
||||
#import "TDColdStartTracker.h"
|
||||
#import "TDHotStartTracker.h"
|
||||
#import "TDInstallTracker.h"
|
||||
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_TITLE;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_URL_PROPERTY;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_REFERRER_URL;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_SCREEN_NAME;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_ELEMENT_ID;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_ELEMENT_TYPE;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_ELEMENT_CONTENT;
|
||||
FOUNDATION_EXTERN NSString * const TD_EVENT_PROPERTY_ELEMENT_POSITION;
|
||||
|
||||
@interface TDAutoTrackManager : NSObject
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
- (void)trackEventView:(UIView *)view;
|
||||
|
||||
- (void)trackEventView:(UIView *)view withIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)trackWithAppid:(NSString *)appid withOption:(ThinkingAnalyticsAutoTrackEventType)type;
|
||||
|
||||
- (void)viewControlWillAppear:(UIViewController *)controller;
|
||||
|
||||
+ (UIViewController *)topPresentedViewController;
|
||||
|
||||
#pragma mark - UNAVAILABLE
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1625f60723f8542a69466be0231920f5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,764 @@
|
||||
#import "TDAutoTrackManager.h"
|
||||
|
||||
#import "TDSwizzler.h"
|
||||
#import "UIViewController+AutoTrack.h"
|
||||
#import "NSObject+TDSwizzle.h"
|
||||
#import "TDJSONUtil.h"
|
||||
#import "UIApplication+AutoTrack.h"
|
||||
#import "ThinkingAnalyticsSDKPrivate.h"
|
||||
#import "TDPublicConfig.h"
|
||||
#import "TAAutoClickEvent.h"
|
||||
#import "TAAutoPageViewEvent.h"
|
||||
#import "TAAppLifeCycle.h"
|
||||
#import "TDAppState.h"
|
||||
#import "TDRunTime.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
|
||||
#import "TAAppStartEvent.h"
|
||||
#import "TAAppEndEvent.h"
|
||||
#import "TDAppEndTracker.h"
|
||||
#import "TDColdStartTracker.h"
|
||||
#import "TDInstallTracker.h"
|
||||
#import "TDAppState.h"
|
||||
|
||||
#ifndef TD_LOCK
|
||||
#define TD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
#endif
|
||||
|
||||
#ifndef TD_UNLOCK
|
||||
#define TD_UNLOCK(lock) dispatch_semaphore_signal(lock);
|
||||
#endif
|
||||
|
||||
NSString * const TD_EVENT_PROPERTY_TITLE = @"#title";
|
||||
NSString * const TD_EVENT_PROPERTY_URL_PROPERTY = @"#url";
|
||||
NSString * const TD_EVENT_PROPERTY_REFERRER_URL = @"#referrer";
|
||||
NSString * const TD_EVENT_PROPERTY_SCREEN_NAME = @"#screen_name";
|
||||
NSString * const TD_EVENT_PROPERTY_ELEMENT_ID = @"#element_id";
|
||||
NSString * const TD_EVENT_PROPERTY_ELEMENT_TYPE = @"#element_type";
|
||||
NSString * const TD_EVENT_PROPERTY_ELEMENT_CONTENT = @"#element_content";
|
||||
NSString * const TD_EVENT_PROPERTY_ELEMENT_POSITION = @"#element_position";
|
||||
|
||||
@interface TDAutoTrackManager ()
|
||||
@property (atomic, strong) NSMutableDictionary<NSString *, id> *autoTrackOptions;
|
||||
@property (nonatomic, strong, nonnull) dispatch_semaphore_t trackOptionLock;
|
||||
@property (atomic, copy) NSString *referrerViewControllerUrl;
|
||||
@property (nonatomic, strong) TDHotStartTracker *appHotStartTracker;
|
||||
@property (nonatomic, strong) TDAppEndTracker *appEndTracker;
|
||||
@property (nonatomic, strong) TDColdStartTracker *appColdStartTracker;
|
||||
@property (nonatomic, strong) TDInstallTracker *appInstallTracker;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TDAutoTrackManager
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static dispatch_once_t once;
|
||||
static TDAutoTrackManager *manager = nil;
|
||||
dispatch_once(&once, ^{
|
||||
manager = [[[TDAutoTrackManager class] alloc] init];
|
||||
manager.autoTrackOptions = [NSMutableDictionary new];
|
||||
manager.trackOptionLock = dispatch_semaphore_create(1);
|
||||
[manager registerAppLifeCycleListener];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (void)trackEventView:(UIView *)view {
|
||||
[self trackEventView:view withIndexPath:nil];
|
||||
}
|
||||
|
||||
- (void)trackEventView:(UIView *)view withIndexPath:(NSIndexPath *)indexPath {
|
||||
if (view.thinkingAnalyticsIgnoreView) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *elementId = nil;
|
||||
NSString *elementType = nil;
|
||||
NSString *elementContent = nil;
|
||||
NSString *elementPosition = nil;
|
||||
NSString *elementPageTitle = nil;
|
||||
NSString *elementScreenName = nil;
|
||||
NSMutableDictionary *yx_customProperties = [NSMutableDictionary dictionary];
|
||||
|
||||
|
||||
elementId = view.thinkingAnalyticsViewID;
|
||||
elementType = NSStringFromClass([view class]);
|
||||
|
||||
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_ID] = view.thinkingAnalyticsViewID;
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_TYPE] = NSStringFromClass([view class]);
|
||||
UIViewController *viewController = [self viewControllerForView:view];
|
||||
if (viewController != nil) {
|
||||
NSString *screenName = NSStringFromClass([viewController class]);
|
||||
properties[TD_EVENT_PROPERTY_SCREEN_NAME] = screenName;
|
||||
|
||||
elementScreenName = screenName;
|
||||
|
||||
NSString *controllerTitle = [self titleFromViewController:viewController];
|
||||
if (controllerTitle) {
|
||||
properties[TD_EVENT_PROPERTY_TITLE] = controllerTitle;
|
||||
|
||||
elementPageTitle = controllerTitle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NSDictionary *propDict = view.thinkingAnalyticsViewProperties;
|
||||
if ([propDict isKindOfClass:[NSDictionary class]]) {
|
||||
[properties addEntriesFromDictionary:propDict];
|
||||
|
||||
[yx_customProperties addEntriesFromDictionary:propDict];
|
||||
}
|
||||
|
||||
UIView *contentView;
|
||||
NSDictionary *propertyWithAppid;
|
||||
if (indexPath) {
|
||||
if ([view isKindOfClass:[UITableView class]]) {
|
||||
UITableView *tableView = (UITableView *)view;
|
||||
contentView = [tableView cellForRowAtIndexPath:indexPath];
|
||||
if (!contentView) {
|
||||
[tableView layoutIfNeeded];
|
||||
contentView = [tableView cellForRowAtIndexPath:indexPath];
|
||||
}
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row];
|
||||
|
||||
elementPosition = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row];
|
||||
|
||||
if ([tableView.thinkingAnalyticsDelegate conformsToProtocol:@protocol(TDUIViewAutoTrackDelegate)]) {
|
||||
if ([tableView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalytics_tableView:autoTrackPropertiesAtIndexPath:)]) {
|
||||
NSDictionary *dic = [view.thinkingAnalyticsDelegate thinkingAnalytics_tableView:tableView autoTrackPropertiesAtIndexPath:indexPath];
|
||||
if ([dic isKindOfClass:[NSDictionary class]]) {
|
||||
[properties addEntriesFromDictionary:dic];
|
||||
|
||||
[yx_customProperties addEntriesFromDictionary:dic];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ([tableView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalyticsWithAppid_tableView:autoTrackPropertiesAtIndexPath:)]) {
|
||||
propertyWithAppid = [view.thinkingAnalyticsDelegate thinkingAnalyticsWithAppid_tableView:tableView autoTrackPropertiesAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
} else if ([view isKindOfClass:[UICollectionView class]]) {
|
||||
UICollectionView *collectionView = (UICollectionView *)view;
|
||||
contentView = [collectionView cellForItemAtIndexPath:indexPath];
|
||||
if (!contentView) {
|
||||
[collectionView layoutIfNeeded];
|
||||
contentView = [collectionView cellForItemAtIndexPath:indexPath];
|
||||
}
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row];
|
||||
|
||||
elementPosition = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row];
|
||||
|
||||
if ([collectionView.thinkingAnalyticsDelegate conformsToProtocol:@protocol(TDUIViewAutoTrackDelegate)]) {
|
||||
if ([collectionView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalytics_collectionView:autoTrackPropertiesAtIndexPath:)]) {
|
||||
NSDictionary *dic = [view.thinkingAnalyticsDelegate thinkingAnalytics_collectionView:collectionView autoTrackPropertiesAtIndexPath:indexPath];
|
||||
if ([dic isKindOfClass:[NSDictionary class]]) {
|
||||
[properties addEntriesFromDictionary:dic];
|
||||
|
||||
[yx_customProperties addEntriesFromDictionary:dic];
|
||||
|
||||
}
|
||||
}
|
||||
if ([collectionView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalyticsWithAppid_collectionView:autoTrackPropertiesAtIndexPath:)]) {
|
||||
propertyWithAppid = [view.thinkingAnalyticsDelegate thinkingAnalyticsWithAppid_collectionView:collectionView autoTrackPropertiesAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contentView = view;
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [TDAutoTrackManager getPosition:contentView];
|
||||
|
||||
elementPosition = [TDAutoTrackManager getPosition:contentView];
|
||||
|
||||
}
|
||||
|
||||
NSString *content = [TDAutoTrackManager getText:contentView];
|
||||
if (content.length > 0) {
|
||||
properties[TD_EVENT_PROPERTY_ELEMENT_CONTENT] = content;
|
||||
|
||||
elementContent = content;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
NSDate *trackDate = [NSDate date];
|
||||
for (NSString *appid in self.autoTrackOptions) {
|
||||
|
||||
ThinkingAnalyticsAutoTrackEventType type = (ThinkingAnalyticsAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppClick) {
|
||||
|
||||
|
||||
|
||||
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
NSMutableDictionary *trackProperties = [properties mutableCopy];
|
||||
|
||||
NSMutableDictionary *yx_trackProperties = [yx_customProperties mutableCopy];
|
||||
|
||||
if ([instance isViewTypeIgnored:[view class]]) {
|
||||
continue;
|
||||
}
|
||||
NSDictionary *ignoreViews = view.thinkingAnalyticsIgnoreViewWithAppid;
|
||||
if (ignoreViews != nil && [[ignoreViews objectForKey:appid] isKindOfClass:[NSNumber class]]) {
|
||||
BOOL ignore = [[ignoreViews objectForKey:appid] boolValue];
|
||||
if (ignore)
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([instance isViewControllerIgnored:viewController]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSDictionary *viewIDs = view.thinkingAnalyticsViewIDWithAppid;
|
||||
if (viewIDs != nil && [viewIDs objectForKey:appid]) {
|
||||
trackProperties[TD_EVENT_PROPERTY_ELEMENT_ID] = [viewIDs objectForKey:appid];
|
||||
|
||||
elementId = [viewIDs objectForKey:appid];
|
||||
|
||||
}
|
||||
|
||||
NSDictionary *viewProperties = view.thinkingAnalyticsViewPropertiesWithAppid;
|
||||
if (viewProperties != nil && [viewProperties objectForKey:appid]) {
|
||||
NSDictionary *properties = [viewProperties objectForKey:appid];
|
||||
if ([properties isKindOfClass:[NSDictionary class]]) {
|
||||
[trackProperties addEntriesFromDictionary:properties];
|
||||
|
||||
[yx_trackProperties addEntriesFromDictionary:properties];
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyWithAppid) {
|
||||
NSDictionary *autoTrackproperties = [propertyWithAppid objectForKey:appid];
|
||||
if ([autoTrackproperties isKindOfClass:[NSDictionary class]]) {
|
||||
[trackProperties addEntriesFromDictionary:autoTrackproperties];
|
||||
|
||||
[yx_trackProperties addEntriesFromDictionary:autoTrackproperties];
|
||||
}
|
||||
}
|
||||
|
||||
TAAutoClickEvent *yx_event = [[TAAutoClickEvent alloc] initWithName:TD_APP_CLICK_EVENT];
|
||||
yx_event.time = trackDate;
|
||||
yx_event.elementId = elementId;
|
||||
yx_event.elementType = elementType;
|
||||
yx_event.elementContent = elementContent;
|
||||
yx_event.elementPosition = elementPosition;
|
||||
yx_event.pageTitle = elementPageTitle;
|
||||
yx_event.screenName = elementScreenName;
|
||||
|
||||
[instance autoTrackWithEvent:yx_event properties:yx_trackProperties];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)trackWithAppid:(NSString *)appid withOption:(ThinkingAnalyticsAutoTrackEventType)type {
|
||||
TD_LOCK(self.trackOptionLock);
|
||||
self.autoTrackOptions[appid] = @(type);
|
||||
TD_UNLOCK(self.trackOptionLock);
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppClick || type & ThinkingAnalyticsEventTypeAppViewScreen) {
|
||||
[self swizzleVC];
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppInstall) {
|
||||
TAAutoTrackEvent *event = [[TAAutoTrackEvent alloc] initWithName:TD_APP_INSTALL_EVENT];
|
||||
event.time = [[NSDate date] dateByAddingTimeInterval: -1];
|
||||
[self.appInstallTracker trackWithInstanceTag:appid event:event params:nil];
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppEnd) {
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
[instance timeEvent:TD_APP_END_EVENT];
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppStart) {
|
||||
dispatch_block_t mainThreadBlock = ^(){
|
||||
NSString *eventName = [TDAppState shareInstance].relaunchInBackground ? TD_APP_START_BACKGROUND_EVENT : TD_APP_START_EVENT;
|
||||
TAAppStartEvent *event = [[TAAppStartEvent alloc] initWithName:eventName];
|
||||
event.resumeFromBackground = NO;
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSString *reason = [TDRunTime getAppLaunchReason];
|
||||
if (reason && reason.length) {
|
||||
event.startReason = reason;
|
||||
}
|
||||
}
|
||||
[self.appColdStartTracker trackWithInstanceTag:appid event:event params:nil];
|
||||
};
|
||||
dispatch_async(dispatch_get_main_queue(), mainThreadBlock);
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppViewCrash) {
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
[[ThinkingExceptionHandler sharedHandler] addThinkingInstance:instance];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewControlWillAppear:(UIViewController *)controller {
|
||||
[self trackViewController:controller];
|
||||
}
|
||||
|
||||
+ (UIViewController *)topPresentedViewController {
|
||||
UIWindow *keyWindow = [self findWindow];
|
||||
if (keyWindow != nil && !keyWindow.isKeyWindow) {
|
||||
[keyWindow makeKeyWindow];
|
||||
}
|
||||
|
||||
UIViewController *topController = keyWindow.rootViewController;
|
||||
if ([topController isKindOfClass:[UINavigationController class]]) {
|
||||
topController = [(UINavigationController *)topController topViewController];
|
||||
}
|
||||
while (topController.presentedViewController) {
|
||||
topController = topController.presentedViewController;
|
||||
}
|
||||
return topController;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (BOOL)isAutoTrackEventType:(ThinkingAnalyticsAutoTrackEventType)eventType {
|
||||
BOOL isIgnored = YES;
|
||||
for (NSString *appid in self.autoTrackOptions) {
|
||||
ThinkingAnalyticsAutoTrackEventType type = (ThinkingAnalyticsAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||||
isIgnored = !(type & eventType);
|
||||
if (isIgnored == NO)
|
||||
break;
|
||||
}
|
||||
return !isIgnored;
|
||||
}
|
||||
|
||||
- (UIViewController *)viewControllerForView:(UIView *)view {
|
||||
UIResponder *responder = view.nextResponder;
|
||||
while (responder) {
|
||||
if ([responder isKindOfClass:[UIViewController class]]) {
|
||||
if ([responder isKindOfClass:[UINavigationController class]]) {
|
||||
responder = [(UINavigationController *)responder topViewController];
|
||||
continue;
|
||||
} else if ([responder isKindOfClass:UITabBarController.class]) {
|
||||
responder = [(UITabBarController *)responder selectedViewController];
|
||||
continue;
|
||||
}
|
||||
return (UIViewController *)responder;
|
||||
}
|
||||
responder = responder.nextResponder;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)trackViewController:(UIViewController *)controller {
|
||||
if (![self shouldTrackViewContrller:[controller class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *pageUrl = nil;
|
||||
NSString *pageReferrer = nil;
|
||||
NSString *pageTitle = nil;
|
||||
NSString *pageScreenName = nil;
|
||||
NSMutableDictionary *yx_customProperties = [NSMutableDictionary dictionary];
|
||||
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
[properties setValue:NSStringFromClass([controller class]) forKey:TD_EVENT_PROPERTY_SCREEN_NAME];
|
||||
|
||||
pageScreenName = NSStringFromClass([controller class]);
|
||||
|
||||
NSString *controllerTitle = [self titleFromViewController:controller];
|
||||
if (controllerTitle) {
|
||||
[properties setValue:controllerTitle forKey:TD_EVENT_PROPERTY_TITLE];
|
||||
|
||||
pageTitle = controllerTitle;
|
||||
}
|
||||
|
||||
NSDictionary *autoTrackerAppidDic;
|
||||
if ([controller conformsToProtocol:@protocol(TDAutoTracker)]) {
|
||||
UIViewController<TDAutoTracker> *autoTrackerController = (UIViewController<TDAutoTracker> *)controller;
|
||||
NSDictionary *autoTrackerDic;
|
||||
if ([controller respondsToSelector:@selector(getTrackPropertiesWithAppid)])
|
||||
autoTrackerAppidDic = [autoTrackerController getTrackPropertiesWithAppid];
|
||||
if ([controller respondsToSelector:@selector(getTrackProperties)])
|
||||
autoTrackerDic = [autoTrackerController getTrackProperties];
|
||||
|
||||
if ([autoTrackerDic isKindOfClass:[NSDictionary class]]) {
|
||||
[properties addEntriesFromDictionary:autoTrackerDic];
|
||||
|
||||
[yx_customProperties addEntriesFromDictionary:autoTrackerDic];
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *screenAutoTrackerAppidDic;
|
||||
if ([controller conformsToProtocol:@protocol(TDScreenAutoTracker)]) {
|
||||
UIViewController<TDScreenAutoTracker> *screenAutoTrackerController = (UIViewController<TDScreenAutoTracker> *)controller;
|
||||
if ([screenAutoTrackerController respondsToSelector:@selector(getScreenUrlWithAppid)])
|
||||
screenAutoTrackerAppidDic = [screenAutoTrackerController getScreenUrlWithAppid];
|
||||
if ([screenAutoTrackerController respondsToSelector:@selector(getScreenUrl)]) {
|
||||
NSString *currentUrl = [screenAutoTrackerController getScreenUrl];
|
||||
[properties setValue:currentUrl forKey:TD_EVENT_PROPERTY_URL_PROPERTY];
|
||||
|
||||
pageUrl = currentUrl;
|
||||
|
||||
[properties setValue:_referrerViewControllerUrl forKey:TD_EVENT_PROPERTY_REFERRER_URL];
|
||||
|
||||
pageReferrer = _referrerViewControllerUrl;
|
||||
|
||||
_referrerViewControllerUrl = currentUrl;
|
||||
}
|
||||
}
|
||||
|
||||
NSDate *trackDate = [NSDate date];
|
||||
for (NSString *appid in self.autoTrackOptions) {
|
||||
ThinkingAnalyticsAutoTrackEventType type = [self.autoTrackOptions[appid] integerValue];
|
||||
if (type & ThinkingAnalyticsEventTypeAppViewScreen) {
|
||||
|
||||
|
||||
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
NSMutableDictionary *trackProperties = [properties mutableCopy];
|
||||
|
||||
NSMutableDictionary *yx_trackProperties = [yx_customProperties mutableCopy];
|
||||
|
||||
if ([instance isViewControllerIgnored:controller]
|
||||
|| [instance isViewTypeIgnored:[controller class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (autoTrackerAppidDic && [autoTrackerAppidDic objectForKey:appid]) {
|
||||
NSDictionary *dic = [autoTrackerAppidDic objectForKey:appid];
|
||||
if ([dic isKindOfClass:[NSDictionary class]]) {
|
||||
[trackProperties addEntriesFromDictionary:dic];
|
||||
|
||||
[yx_trackProperties addEntriesFromDictionary:dic];
|
||||
}
|
||||
}
|
||||
|
||||
if (screenAutoTrackerAppidDic && [screenAutoTrackerAppidDic objectForKey:appid]) {
|
||||
NSString *screenUrl = [screenAutoTrackerAppidDic objectForKey:appid];
|
||||
[trackProperties setValue:screenUrl forKey:TD_EVENT_PROPERTY_URL_PROPERTY];
|
||||
|
||||
pageUrl = screenUrl;
|
||||
}
|
||||
|
||||
TAAutoPageViewEvent *pageEvent = [[TAAutoPageViewEvent alloc] initWithName:TD_APP_VIEW_EVENT];
|
||||
pageEvent.time = trackDate;
|
||||
pageEvent.pageUrl = pageUrl;
|
||||
pageEvent.pageTitle = pageTitle;
|
||||
pageEvent.referrer = pageReferrer;
|
||||
pageEvent.screenName = pageScreenName;
|
||||
|
||||
[instance autoTrackWithEvent:pageEvent properties:yx_trackProperties];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldTrackViewContrller:(Class)aClass {
|
||||
return ![TDPublicConfig.controllers containsObject:NSStringFromClass(aClass)];
|
||||
}
|
||||
|
||||
- (ThinkingAnalyticsAutoTrackEventType)autoTrackOptionForAppid:(NSString *)appid {
|
||||
return (ThinkingAnalyticsAutoTrackEventType)[[self.autoTrackOptions objectForKey:appid] integerValue];
|
||||
}
|
||||
|
||||
- (void)swizzleSelected:(UIView *)view delegate:(id)delegate {
|
||||
if ([view isKindOfClass:[UITableView class]]
|
||||
&& [delegate conformsToProtocol:@protocol(UITableViewDelegate)]) {
|
||||
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UITableView *tableView, NSIndexPath *indexPath) {
|
||||
[self trackEventView:tableView withIndexPath:indexPath];
|
||||
};
|
||||
|
||||
[TDSwizzler swizzleSelector:@selector(tableView:didSelectRowAtIndexPath:)
|
||||
onClass:[delegate class]
|
||||
withBlock:block
|
||||
named:@"td_table_select"];
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[UICollectionView class]]
|
||||
&& [delegate conformsToProtocol:@protocol(UICollectionViewDelegate)]) {
|
||||
|
||||
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UICollectionView *collectionView, NSIndexPath *indexPath) {
|
||||
[self trackEventView:collectionView withIndexPath:indexPath];
|
||||
};
|
||||
[TDSwizzler swizzleSelector:@selector(collectionView:didSelectItemAtIndexPath:)
|
||||
onClass:[delegate class]
|
||||
withBlock:block
|
||||
named:@"td_collection_select"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)swizzleVC {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void (^tableViewBlock)(UITableView *tableView,
|
||||
SEL cmd,
|
||||
id<UITableViewDelegate> delegate) =
|
||||
^(UITableView *tableView, SEL cmd, id<UITableViewDelegate> delegate) {
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self swizzleSelected:tableView delegate:delegate];
|
||||
};
|
||||
|
||||
[TDSwizzler swizzleSelector:@selector(setDelegate:)
|
||||
onClass:[UITableView class]
|
||||
withBlock:tableViewBlock
|
||||
named:@"td_table_delegate"];
|
||||
|
||||
void (^collectionViewBlock)(UICollectionView *, SEL, id<UICollectionViewDelegate>) = ^(UICollectionView *collectionView, SEL cmd, id<UICollectionViewDelegate> delegate) {
|
||||
if (nil == delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self swizzleSelected:collectionView delegate:delegate];
|
||||
};
|
||||
[TDSwizzler swizzleSelector:@selector(setDelegate:)
|
||||
onClass:[UICollectionView class]
|
||||
withBlock:collectionViewBlock
|
||||
named:@"td_collection_delegate"];
|
||||
|
||||
|
||||
[UIViewController td_swizzleMethod:@selector(viewWillAppear:)
|
||||
withMethod:@selector(td_autotrack_viewWillAppear:)
|
||||
error:NULL];
|
||||
|
||||
[UIApplication td_swizzleMethod:@selector(sendAction:to:from:forEvent:)
|
||||
withMethod:@selector(td_sendAction:to:from:forEvent:)
|
||||
error:NULL];
|
||||
});
|
||||
}
|
||||
|
||||
+ (NSString *)getPosition:(UIView *)view {
|
||||
NSString *position = nil;
|
||||
if ([view isKindOfClass:[UIView class]] && view.thinkingAnalyticsIgnoreView) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([view isKindOfClass:[UITabBar class]]) {
|
||||
UITabBar *tabbar = (UITabBar *)view;
|
||||
position = [NSString stringWithFormat: @"%ld", (long)[tabbar.items indexOfObject:tabbar.selectedItem]];
|
||||
} else if ([view isKindOfClass:[UISegmentedControl class]]) {
|
||||
UISegmentedControl *segment = (UISegmentedControl *)view;
|
||||
position = [NSString stringWithFormat:@"%ld", (long)segment.selectedSegmentIndex];
|
||||
} else if ([view isKindOfClass:[UIProgressView class]]) {
|
||||
UIProgressView *progress = (UIProgressView *)view;
|
||||
position = [NSString stringWithFormat:@"%f", progress.progress];
|
||||
} else if ([view isKindOfClass:[UIPageControl class]]) {
|
||||
UIPageControl *pageControl = (UIPageControl *)view;
|
||||
position = [NSString stringWithFormat:@"%ld", (long)pageControl.currentPage];
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
+ (NSString *)getText:(NSObject *)obj {
|
||||
NSString *text = nil;
|
||||
if ([obj isKindOfClass:[UIView class]] && [(UIView *)obj thinkingAnalyticsIgnoreView]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([obj isKindOfClass:[UIButton class]]) {
|
||||
text = ((UIButton *)obj).currentTitle;
|
||||
} else if ([obj isKindOfClass:[UITextView class]] ||
|
||||
[obj isKindOfClass:[UITextField class]]) {
|
||||
//ignore
|
||||
} else if ([obj isKindOfClass:[UILabel class]]) {
|
||||
text = ((UILabel *)obj).text;
|
||||
} else if ([obj isKindOfClass:[UIPickerView class]]) {
|
||||
UIPickerView *picker = (UIPickerView *)obj;
|
||||
NSInteger sections = picker.numberOfComponents;
|
||||
NSMutableArray *titles = [NSMutableArray array];
|
||||
|
||||
for(NSInteger i = 0; i < sections; i++) {
|
||||
NSInteger row = [picker selectedRowInComponent:i];
|
||||
NSString *title;
|
||||
if ([picker.delegate
|
||||
respondsToSelector:@selector(pickerView:titleForRow:forComponent:)]) {
|
||||
title = [picker.delegate pickerView:picker titleForRow:row forComponent:i];
|
||||
} else if ([picker.delegate
|
||||
respondsToSelector:@selector(pickerView:attributedTitleForRow:forComponent:)]) {
|
||||
title = [picker.delegate
|
||||
pickerView:picker
|
||||
attributedTitleForRow:row forComponent:i].string;
|
||||
}
|
||||
[titles addObject:title ?: @""];
|
||||
}
|
||||
if (titles.count > 0) {
|
||||
text = [titles componentsJoinedByString:@","];
|
||||
}
|
||||
} else if ([obj isKindOfClass:[UIDatePicker class]]) {
|
||||
UIDatePicker *picker = (UIDatePicker *)obj;
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.dateFormat = kDefaultTimeFormat;
|
||||
text = [formatter stringFromDate:picker.date];
|
||||
} else if ([obj isKindOfClass:[UISegmentedControl class]]) {
|
||||
UISegmentedControl *segment = (UISegmentedControl *)obj;
|
||||
text = [NSString stringWithFormat:@"%@", [segment titleForSegmentAtIndex:segment.selectedSegmentIndex]];
|
||||
} else if ([obj isKindOfClass:[UISwitch class]]) {
|
||||
UISwitch *switchItem = (UISwitch *)obj;
|
||||
text = switchItem.on ? @"on" : @"off";
|
||||
} else if ([obj isKindOfClass:[UISlider class]]) {
|
||||
UISlider *slider = (UISlider *)obj;
|
||||
text = [NSString stringWithFormat:@"%f", [slider value]];
|
||||
} else if ([obj isKindOfClass:[UIStepper class]]) {
|
||||
UIStepper *step = (UIStepper *)obj;
|
||||
text = [NSString stringWithFormat:@"%f", [step value]];
|
||||
} else {
|
||||
if ([obj isKindOfClass:[UIView class]]) {
|
||||
for(UIView *subView in [(UIView *)obj subviews]) {
|
||||
text = [TDAutoTrackManager getText:subView];
|
||||
if ([text isKindOfClass:[NSString class]] && text.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
- (NSString *)titleFromViewController:(UIViewController *)viewController {
|
||||
if (!viewController) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIView *titleView = viewController.navigationItem.titleView;
|
||||
NSString *elementContent = nil;
|
||||
if (titleView) {
|
||||
elementContent = [TDAutoTrackManager getText:titleView];
|
||||
}
|
||||
|
||||
return elementContent.length > 0 ? elementContent : viewController.navigationItem.title;
|
||||
}
|
||||
|
||||
+ (UIWindow *)findWindow {
|
||||
UIApplication *application = [TDAppState sharedApplication];
|
||||
if (![application isKindOfClass:UIApplication.class]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIWindow *window = application.keyWindow;
|
||||
if (window == nil || window.windowLevel != UIWindowLevelNormal) {
|
||||
for (window in application.windows) {
|
||||
if (window.windowLevel == UIWindowLevelNormal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(iOS 13.0, tvOS 13, *)) {
|
||||
NSSet *scenes = [[TDAppState sharedApplication] valueForKey:@"connectedScenes"];
|
||||
for (id scene in scenes) {
|
||||
if (window) {
|
||||
break;
|
||||
}
|
||||
|
||||
id activationState = [scene valueForKeyPath:@"activationState"];
|
||||
BOOL isActive = activationState != nil && [activationState integerValue] == 0;
|
||||
if (isActive) {
|
||||
Class WindowScene = NSClassFromString(@"UIWindowScene");
|
||||
if ([scene isKindOfClass:WindowScene]) {
|
||||
NSArray<UIWindow *> *windows = [scene valueForKeyPath:@"windows"];
|
||||
for (UIWindow *w in windows) {
|
||||
if (w.isKeyWindow) {
|
||||
window = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
//MARK: - App Life Cycle
|
||||
|
||||
- (void)registerAppLifeCycleListener {
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[notificationCenter addObserver:self selector:@selector(appStateWillChangeNotification:) name:kTAAppLifeCycleStateWillChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)appStateWillChangeNotification:(NSNotification *)notification {
|
||||
TAAppLifeCycleState newState = [[notification.userInfo objectForKey:kTAAppLifeCycleNewStateKey] integerValue];
|
||||
TAAppLifeCycleState oldState = [[notification.userInfo objectForKey:kTAAppLifeCycleOldStateKey] integerValue];
|
||||
|
||||
if (newState == TAAppLifeCycleStateStart) {
|
||||
for (NSString *appid in self.autoTrackOptions) {
|
||||
ThinkingAnalyticsAutoTrackEventType type = (ThinkingAnalyticsAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||||
|
||||
// Only open the start event of collecting hot start. Cold start event, reported when automatic collection is turned on
|
||||
if ((type & ThinkingAnalyticsEventTypeAppStart) && oldState != TAAppLifeCycleStateInit) {
|
||||
NSString *eventName = [TDAppState shareInstance].relaunchInBackground ? TD_APP_START_BACKGROUND_EVENT : TD_APP_START_EVENT;
|
||||
TAAppStartEvent *event = [[TAAppStartEvent alloc] initWithName:eventName];
|
||||
event.resumeFromBackground = YES;
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSString *reason = [TDRunTime getAppLaunchReason];
|
||||
if (reason && reason.length) {
|
||||
event.startReason = reason;
|
||||
}
|
||||
}
|
||||
[self.appHotStartTracker trackWithInstanceTag:appid event:event params:@{}];
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppEnd) {
|
||||
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
[instance timeEvent:TD_APP_END_EVENT];
|
||||
}
|
||||
}
|
||||
} else if (newState == TAAppLifeCycleStateEnd) {
|
||||
for (NSString *appid in self.autoTrackOptions) {
|
||||
ThinkingAnalyticsAutoTrackEventType type = (ThinkingAnalyticsAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||||
if (type & ThinkingAnalyticsEventTypeAppEnd) {
|
||||
TAAppEndEvent *event = [[TAAppEndEvent alloc] initWithName:TD_APP_END_EVENT];
|
||||
td_dispatch_main_sync_safe(^{
|
||||
|
||||
NSString *screenName = NSStringFromClass([[TDAutoTrackManager topPresentedViewController] class]);
|
||||
event.screenName = screenName;
|
||||
[self.appEndTracker trackWithInstanceTag:appid event:event params:@{}];
|
||||
});
|
||||
}
|
||||
|
||||
if (type & ThinkingAnalyticsEventTypeAppStart) {
|
||||
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK sharedInstanceWithAppid:appid];
|
||||
[instance timeEvent:TD_APP_START_EVENT];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Getter & Setter
|
||||
|
||||
- (TDHotStartTracker *)appHotStartTracker {
|
||||
if (!_appHotStartTracker) {
|
||||
_appHotStartTracker = [[TDHotStartTracker alloc] init];
|
||||
}
|
||||
return _appHotStartTracker;
|
||||
}
|
||||
|
||||
- (TDColdStartTracker *)appColdStartTracker {
|
||||
if (!_appColdStartTracker) {
|
||||
_appColdStartTracker = [[TDColdStartTracker alloc] init];
|
||||
}
|
||||
return _appColdStartTracker;
|
||||
}
|
||||
|
||||
- (TDInstallTracker *)appInstallTracker {
|
||||
if (!_appInstallTracker) {
|
||||
_appInstallTracker = [[TDInstallTracker alloc] init];
|
||||
}
|
||||
return _appInstallTracker;
|
||||
}
|
||||
|
||||
- (TDAppEndTracker *)appEndTracker {
|
||||
if (!_appEndTracker) {
|
||||
_appEndTracker = [[TDAppEndTracker alloc] init];
|
||||
}
|
||||
return _appEndTracker;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4df3c68e4c10242c4a93e8087f0cc5bd
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c350cba01555c41e4aceb6039c8408a8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user