[M] sync n3

This commit is contained in:
2024-12-09 16:54:19 +08:00
parent c90bbe86e7
commit 05331e6286
845 changed files with 6138 additions and 1307 deletions

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0f4211ddaee63483da2cf08a3b9dbed6
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: d606670f7a7c24e1d86b03c1fc6251ac
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 08ddbde4b0bb44a2781691f28d329404
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 0ad802e76f93c418c9419315b8a3d275
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: ed11ab70d6d6943019d6d3995eeaadc2
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 47013b120f73c4885845dbb7dd840189
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b2eec12b437e64f6886ebe51d6680b83
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: dd9eda5b6572748ecaa3313c288c9e16
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: a8c262d70355c46f38a4fce70eb95cc5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b039553b9411c4e30b4fc767b7c2714d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 17463cb6f209545bcbc20aad5129804d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: dd78c300cef5f4507a13b638ce74c1e5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 73166a65f020c46e8a3c53cfa301c5f2
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0aa41df3a429a4fd8915122407b7001a
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: c165be5fc3eea4ab0bd9ff520104d3d8
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 852dc5d94530d4aff8a96973c9986ad2
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b05b7c406ad1f43d0bb4f1be994d8765
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: aec2711162866493884cdef2cc925bb0
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: d844d7614eae8439c904288502d39b52
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 814fa739d0a0447259f0fda304b44e4e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b66965aa8d4f84c9fbcab5bb32c159a6
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b6c49449459eb4a92bf83ab85a7be1e7
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: b1cc9da14eecc4d23b345a15404edcab
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 009c284b5153443e8bff5c9ca6ba32e8
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b294828e3aa214401ad6a0db7ebd8a63
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: c2a9df7cedc0046aaad72584161c53f8
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: d8ab506fca17c4ec1815a15a2b7eb5f7
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: f37b994396cdc4d7780bcb516fcc6d51
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 2153a11937bd248f793246e3f848aaab
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 20131090d388c4844a66a22ba19bcf58
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: 885238a5636ac43b7a9be0bfdd0537b2
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 90a768aa97d9f4a67a459208b6dc419f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: bfe06ac7bb20d44f4b38427283e52402
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 439283b9eaf2e4d26883e97a319af143
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 60e81b49c2ab941b48f1746f0b7f409d
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: ab91a24abdc494f7b8c0862a2b45cce3
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 294d54bcf09bf453c8bf7febc52d1dcb
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 77af5449a173c4d05aa967435187b647
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 1294c9d002c2c4f879fcc49269cbc7e9
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: e0b6e9b4fa84e40ffa4799dd7aed576d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 22c8f205dbc9a492db570c08357cab4f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: dfeb4b3d07e4d4c0e89b139b8d7e4184
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 2c0bed1add07542e6bcf1fda661ce0d5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: debe332a658c34f4a97564ba712921a6
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 457a15183a12f43288ad47022c24f21d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 5da924cd82c7e49b6a2d9eafec038882
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 70ccea408f557438a9f8be087342eab3
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 18165eaf66907443c93552539b274cd0
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 0346fe03091074a4ab4c1763dc7d6d5f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 1625f60723f8542a69466be0231920f5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

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: 4df3c68e4c10242c4a93e8087f0cc5bd
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

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

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