Ta android + ios

This commit is contained in:
LYP
2024-08-07 20:22:28 +08:00
parent 69fee08c61
commit 76458664d8
722 changed files with 41709 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 274775549bf01f14ba0369ee739f32bd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2e585adef85fe2c46a99fcafc29c636a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 32c5d3589963cd04d91efdf431b4250e
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:

View File

@@ -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;
}

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: a2b06d0e8887f27468ea036fd26d2a42
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:

View File

@@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TDAppDelegateProxyManager : NSObject
+ (instancetype)defaultManager;
- (void)proxyNotifications;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 4410b4aaaa5a62549a9c6d948a8d1577
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: abfec29fce672064aab3a832900a873e
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:

View File

@@ -0,0 +1,10 @@
#import "TDDelegateProxy.h"
NS_ASSUME_NONNULL_BEGIN
@interface TDApplicationDelegateProxy : TDDelegateProxy
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: ca10079e00bccb14a87fcf2f6bd06b08
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 6fcf40a52d81b34458cb1a00a973d114
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 805747b0abfc919409c3f41976e0da46
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 0e1d9a52ad6d1bb448047d9511f65af6
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: d1079c5db6551594781ca867e7f6839e
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: cfc60789a07708e4da5c6c0d000e37ee
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 649e54e5cf5169f46806d54fb005049f
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 450f884bacd370f45b28a1869861f2d8
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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9fdc832dd8173149b1df9df00164436
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: b9402120bfa12c849bdbf985c51c0535
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 7c8d3b9224659564b9fe4db839be6c5b
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 7e6c0a59660b31f419175804f218c192
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 420d8a217a542bf4981ba78f9aed757c
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 4777c08c43760724ab9649b8c1a1752d
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 315c41452d35ed64aa832c93b52b2b3a
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 85ad8b65ff9356a47a3a7ae39c279ddb
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: bc0971c2929b9c34095463469ddf31cd
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 4b944f39f6447d9459b90a9b3dcf34e8
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 0b2861556d1c5c44eac95b8110fb3481
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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c523c7ccc8260b4296c4f5e308de62a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: f13e46f9ceae76041a668ce21ee63c12
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 83e5e45c60e92754d917ed33e60f7552
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 265a13baa4b98574a93342705cae65eb
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 504f113ee087c6c40bbe3ab24800661c
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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ddeb7d21ec2b50d40b2d4448337958ff
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 610fdfd3759c45f439e92e6bf7db0aa2
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: c63bdf355fa2241419ab65c6105d3acf
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: c75a2cc6ed1843642a1e845ac0df06f8
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 624b80054b0aaa046bd67b8df72def6f
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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6a2a6dbeec53a564e855dfaecb9f6128
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f8b2a224e9edc24da27dd4ea03a3b39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 40a6155cb5c236b4b8087a56f07b6b99
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 70f1894193c33dd49b8c7a95f3483903
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 10585deadfd0a0b41b8af66b473a82d1
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 7dc79746c3bbc9e448ed4c906d915101
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: de4bf411c264c504a864d1110c8edb45
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 6822b7c6562f6234992a3529f37709f0
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: f3496858b20c04445b91102798553dc1
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 80c9b30ac6f8e9f4793c4c456666088f
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3090a87679ab84a4abee1eec9aad1b89
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: b29666e1c445d994991de96212385d20
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: fec359995ca52d242b7b0857f45a3b4d
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: d30d06823cd9afb49b3635a39a6adcf5
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:

View File

@@ -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 */

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: f5cf0e391e1be6f4dae7d1d501d2efaa
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:

View File

@@ -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 */

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: e5d98e43931f6fd4a8ccf60e1c84f678
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 8b472c2607fe7874b967a410a658ac97
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:

View File

@@ -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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: e28ed5bde9eff6e4ead5f94647ed98c3
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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 462bc89e58151374fbaed6eab40f2d41
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More