Ta android + ios
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e585adef85fe2c46a99fcafc29c636a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TDAPPPushParams.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Charles on 6.5.23.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern id __td_get_userNotificationCenter(void);
|
||||
extern id __td_get_userNotificationCenter_delegate(void);
|
||||
extern NSDictionary * __td_get_userNotificationCenterResponse(id response);
|
||||
extern NSString * __td_get_userNotificationCenterRequestContentTitle(id response);
|
||||
extern NSString * __td_get_userNotificationCenterRequestContentBody(id response);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32c5d3589963cd04d91efdf431b4250e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// TDAPPPushParams.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by Charles on 6.5.23.
|
||||
//
|
||||
|
||||
#import "TDAPPPushParams.h"
|
||||
|
||||
id __td_get_userNotificationCenter(void) {
|
||||
Class cls = NSClassFromString(@"UNUserNotificationCenter");
|
||||
SEL sel = NSSelectorFromString(@"currentNotificationCenter");
|
||||
if ([cls respondsToSelector:sel]) {
|
||||
id (*getUserNotificationCenterIMP)(id, SEL) = (NSString * (*)(id, SEL))[cls methodForSelector:sel];
|
||||
return getUserNotificationCenterIMP(cls, sel);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
id __td_get_userNotificationCenter_delegate(void) {
|
||||
Class cls = NSClassFromString(@"UNUserNotificationCenter");
|
||||
SEL sel = NSSelectorFromString(@"currentNotificationCenter");
|
||||
SEL delegateSel = NSSelectorFromString(@"delegate");
|
||||
if ([cls respondsToSelector:sel]) {
|
||||
id (*getUserNotificationCenterIMP)(id, SEL) = (id (*)(id, SEL))[cls methodForSelector:sel];
|
||||
id center = getUserNotificationCenterIMP(cls, sel);
|
||||
if (center) {
|
||||
id (*getUserNotificationCenterDelegateIMP)(id, SEL) = (id (*)(id, SEL))[center methodForSelector:delegateSel];
|
||||
id delegate = getUserNotificationCenterDelegateIMP(center, delegateSel);
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary * __td_get_userNotificationCenterResponse(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.userInfo"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * __td_get_userNotificationCenterRequestContentTitle(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.title"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * __td_get_userNotificationCenterRequestContentBody(id response) {
|
||||
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
return [response valueForKeyPath:@"notification.request.content.body"];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2b06d0e8887f27468ea036fd26d2a42
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDAppDelegateProxyManager : NSObject
|
||||
|
||||
+ (instancetype)defaultManager;
|
||||
|
||||
- (void)proxyNotifications;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4410b4aaaa5a62549a9c6d948a8d1577
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
#import "TDAppDelegateProxyManager.h"
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import "TDNewSwizzle.h"
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import "TDMethodHelper.h"
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
#endif
|
||||
|
||||
@implementation TDAppDelegateProxyManager
|
||||
|
||||
+ (instancetype)defaultManager {
|
||||
static dispatch_once_t onceToken;
|
||||
static TDAppDelegateProxyManager *manager = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[TDAppDelegateProxyManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (void)proxyNotifications NS_EXTENSION_UNAVAILABLE_IOS(""){
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[TDMethodHelper swizzleRespondsToSelector];
|
||||
|
||||
[TDApplicationDelegateProxy resolveOptionalSelectorsForDelegate:[UIApplication sharedApplication].delegate];
|
||||
[TDApplicationDelegateProxy proxyDelegate:[UIApplication sharedApplication].delegate selectors:[NSSet setWithArray:@[@"application:didReceiveLocalNotification:",
|
||||
@"application:didReceiveRemoteNotification:fetchCompletionHandler:",
|
||||
@"application:handleOpenURL:",
|
||||
@"application:openURL:options:",
|
||||
@"application:continueUserActivity:restorationHandler:",
|
||||
@"application:performActionForShortcutItem:completionHandler:"]]];
|
||||
if (@available(iOS 10.0, *)) {
|
||||
if ([UNUserNotificationCenter currentNotificationCenter].delegate) {
|
||||
[TDUNUserNotificationCenterDelegateProxy proxyDelegate:[UNUserNotificationCenter currentNotificationCenter].delegate selectors:[NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]]];
|
||||
}
|
||||
NSError *error = NULL;
|
||||
[UNUserNotificationCenter td_new_swizzleMethod:@selector(setDelegate:) withMethod:@selector(thinkingdata_setDelegate:) error:&error];
|
||||
if (error) {
|
||||
NSLog(@"proxy notification delegate error: %@", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abfec29fce672064aab3a832900a873e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDApplicationDelegateProxy : TDDelegateProxy
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca10079e00bccb14a87fcf2f6bd06b08
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import <objc/message.h>
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import "TDCommonUtil.h"
|
||||
|
||||
@implementation TDApplicationDelegateProxy
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, userInfo, completionHandler];
|
||||
[TDApplicationDelegateProxy trackEventWithTarget:self application:application remoteNotification:userInfo];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
||||
SEL selector = @selector(application:didReceiveLocalNotification:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, notification];
|
||||
[TDApplicationDelegateProxy trackEventWithTarget:self application:application localNotification:notification];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
|
||||
SEL selector = @selector(application:continueUserActivity:restorationHandler:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string: userActivity.webpageURL.absoluteString],@"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:application arg2:userActivity arg3:restorationHandler];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
SEL selector = @selector(application:openURL:options:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string:url.absoluteString],@"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:app arg2:url arg3:options];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
|
||||
SEL selector = @selector(application:handleOpenURL:);
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": [TDCommonUtil string:url.absoluteString], @"data":@{}};
|
||||
}
|
||||
return [TDApplicationDelegateProxy invokeReturnBOOLWithTarget:self selector:selector arg1:application arg2:url];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler{
|
||||
SEL selector = @selector(application:performActionForShortcutItem:completionHandler:);
|
||||
[TDApplicationDelegateProxy invokeWithTarget:self selector:selector, application, shortcutItem, completionHandler];
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"",@"data": [TDCommonUtil dictionary:shortcutItem.userInfo]};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target application:(UIApplication *)application remoteNotification:(NSDictionary *)userInfo {
|
||||
|
||||
if (target != application.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
|
||||
NSLog(@"iOS version >= 10.0, callback for %@ was ignored.", @"application:didReceiveRemoteNotification:fetchCompletionHandler:");
|
||||
return;
|
||||
}
|
||||
|
||||
if (application.applicationState != UIApplicationStateInactive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:userInfo]};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target application:(UIApplication *)application localNotification:(UILocalNotification *)notification {
|
||||
|
||||
if (target != application.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isValidPushClick = NO;
|
||||
if (application.applicationState == UIApplicationStateInactive) {
|
||||
isValidPushClick = YES;
|
||||
}
|
||||
|
||||
if (!isValidPushClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
properties[@"alertBody"] = notification.alertBody;
|
||||
if (@available(iOS 8.2, *)) {
|
||||
properties[@"alertTitle"] = notification.alertTitle;
|
||||
}
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:properties]};
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSSet<NSString *> *)optionalSelectors {
|
||||
return [NSSet setWithArray:@[@"application:didReceiveLocalNotification:",
|
||||
@"application:didReceiveRemoteNotification:fetchCompletionHandler:",
|
||||
@"application:handleOpenURL:",
|
||||
@"application:openURL:options:",
|
||||
@"application:continueUserActivity:restorationHandler:",
|
||||
@"application:performActionForShortcutItem:completionHandler:"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fcf40a52d81b34458cb1a00a973d114
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDUNUserNotificationCenterDelegateProxy : TDDelegateProxy
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 805747b0abfc919409c3f41976e0da46
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/message.h>
|
||||
#import "TDAPPPushParams.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import "TDCommonUtil.h"
|
||||
|
||||
@implementation TDUNUserNotificationCenterDelegateProxy
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
|
||||
SEL selector = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
|
||||
[TDUNUserNotificationCenterDelegateProxy invokeWithTarget:self selector:selector, center, response, completionHandler];
|
||||
[TDUNUserNotificationCenterDelegateProxy trackEventWithTarget:self notificationCenter:center notificationResponse:response];
|
||||
}
|
||||
|
||||
+ (void)trackEventWithTarget:(NSObject *)target notificationCenter:(UNUserNotificationCenter *)center notificationResponse:(UNNotificationResponse *)response API_AVAILABLE(ios(10.0)){
|
||||
|
||||
if (target != center.delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary *userInfo = __td_get_userNotificationCenterResponse(response);
|
||||
if (userInfo) {
|
||||
[properties addEntriesFromDictionary:userInfo];
|
||||
}
|
||||
properties[@"title"] = __td_get_userNotificationCenterRequestContentTitle(response);
|
||||
properties[@"body"] = __td_get_userNotificationCenterRequestContentBody(response);
|
||||
|
||||
[[TDAppLaunchReason sharedInstance] clearAppLaunchParams];
|
||||
[TDAppLaunchReason sharedInstance].appLaunchParams = @{@"url": @"", @"data": [TDCommonUtil dictionary:properties]};
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableOpsReceiptProperties]) {
|
||||
@try {
|
||||
if ([response isKindOfClass:[NSClassFromString(@"UNNotificationResponse") class]]) {
|
||||
NSDictionary *userInfo = [response valueForKeyPath:@"notification.request.content.userInfo"];
|
||||
[TDAppLaunchReason td_ops_push_click:userInfo];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+ (NSSet<NSString *> *)optionalSelectors {
|
||||
return [NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e1d9a52ad6d1bb448047d9511f65af6
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIApplication (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id <UIApplicationDelegate>)delegate;
|
||||
@property (nonatomic, copy, nullable) NSDictionary *thinkingdata_launchOptions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1079c5db6551594781ca867e7f6839e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
#import "UIApplication+TDPushClick.h"
|
||||
#import "TDApplicationDelegateProxy.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static void *const kSALaunchOptions = (void *)&kSALaunchOptions;
|
||||
|
||||
@implementation UIApplication (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id<UIApplicationDelegate>)delegate {
|
||||
[TDApplicationDelegateProxy resolveOptionalSelectorsForDelegate:delegate];
|
||||
|
||||
[self thinkingdata_setDelegate:delegate];
|
||||
|
||||
if (!self.delegate) {
|
||||
return;
|
||||
}
|
||||
[TDApplicationDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"application:didReceiveLocalNotification:", @"application:didReceiveRemoteNotification:fetchCompletionHandler:"]]];
|
||||
}
|
||||
|
||||
- (NSDictionary *)thinkingdata_launchOptions {
|
||||
return objc_getAssociatedObject(self, kSALaunchOptions);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_launchOptions:(NSDictionary *)thinkingdata_launchOptions {
|
||||
objc_setAssociatedObject(self, kSALaunchOptions, thinkingdata_launchOptions, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfc60789a07708e4da5c6c0d000e37ee
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UNUserNotificationCenter (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id <UNUserNotificationCenterDelegate>)delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 649e54e5cf5169f46806d54fb005049f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
#import "UNUserNotificationCenter+TDPushClick.h"
|
||||
#import "TDUNUserNotificationCenterDelegateProxy.h"
|
||||
|
||||
@implementation UNUserNotificationCenter (TDPushClick)
|
||||
|
||||
- (void)thinkingdata_setDelegate:(id<UNUserNotificationCenterDelegate>)delegate {
|
||||
|
||||
[TDUNUserNotificationCenterDelegateProxy resolveOptionalSelectorsForDelegate:delegate];
|
||||
|
||||
[self thinkingdata_setDelegate:delegate];
|
||||
if (!self.delegate) {
|
||||
return;
|
||||
}
|
||||
[TDUNUserNotificationCenterDelegateProxy proxyDelegate:self.delegate selectors:[NSSet setWithArray:@[@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"]]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 450f884bacd370f45b28a1869861f2d8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9fdc832dd8173149b1df9df00164436
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TDDelegateProxyObject.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (TDDelegateProxy)
|
||||
|
||||
@property (nonatomic, copy, nullable) NSSet<NSString *> *thinkingdata_optionalSelectors;
|
||||
@property (nonatomic, strong, nullable) TDDelegateProxyObject *thinkingdata_delegateObject;
|
||||
|
||||
/// hook respondsToSelector to resolve optional selectors
|
||||
/// @param aSelector selector
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSProxy (TDDelegateProxy)
|
||||
|
||||
@property (nonatomic, copy, nullable) NSSet<NSString *> *thinkingdata_optionalSelectors;
|
||||
@property (nonatomic, strong, nullable) TDDelegateProxyObject *thinkingdata_delegateObject;
|
||||
|
||||
/// hook respondsToSelector to resolve optional selectors
|
||||
/// @param aSelector selector
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9402120bfa12c849bdbf985c51c0535
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static void *const kTDNSObjectDelegateOptionalSelectorsKey = (void *)&kTDNSObjectDelegateOptionalSelectorsKey;
|
||||
static void *const kTDNSObjectDelegateObjectKey = (void *)&kTDNSObjectDelegateObjectKey;
|
||||
|
||||
static void *const kTDNSProxyDelegateOptionalSelectorsKey = (void *)&kTDNSProxyDelegateOptionalSelectorsKey;
|
||||
static void *const kTDNSProxyDelegateObjectKey = (void *)&kTDNSProxyDelegateObjectKey;
|
||||
|
||||
@implementation NSObject (TDDelegateProxy)
|
||||
|
||||
- (NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
return objc_getAssociatedObject(self, kTDNSObjectDelegateOptionalSelectorsKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_optionalSelectors:(NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
objc_setAssociatedObject(self, kTDNSObjectDelegateOptionalSelectorsKey, thinkingdata_optionalSelectors, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
- (TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
return objc_getAssociatedObject(self, kTDNSObjectDelegateObjectKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_delegateObject:(TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
objc_setAssociatedObject(self, kTDNSObjectDelegateObjectKey, thinkingdata_delegateObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector {
|
||||
if ([self thinkingdata_respondsToSelector:aSelector]) {
|
||||
return YES;
|
||||
}
|
||||
if ([self.thinkingdata_optionalSelectors containsObject:NSStringFromSelector(aSelector)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSProxy (TDDelegateProxy)
|
||||
|
||||
- (NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
return objc_getAssociatedObject(self, kTDNSProxyDelegateOptionalSelectorsKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_optionalSelectors:(NSSet<NSString *> *)thinkingdata_optionalSelectors {
|
||||
objc_setAssociatedObject(self, kTDNSProxyDelegateOptionalSelectorsKey, thinkingdata_optionalSelectors, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
- (TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
return objc_getAssociatedObject(self, kTDNSProxyDelegateObjectKey);
|
||||
}
|
||||
|
||||
- (void)setThinkingdata_delegateObject:(TDDelegateProxyObject *)thinkingdata_delegateObject {
|
||||
objc_setAssociatedObject(self, kTDNSProxyDelegateObjectKey, thinkingdata_delegateObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (BOOL)thinkingdata_respondsToSelector:(SEL)aSelector {
|
||||
if ([self thinkingdata_respondsToSelector:aSelector]) {
|
||||
return YES;
|
||||
}
|
||||
if ([self.thinkingdata_optionalSelectors containsObject:NSStringFromSelector(aSelector)]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c8d3b9224659564b9fe4db839be6c5b
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDClassHelper : NSObject
|
||||
|
||||
+ (Class _Nullable)allocateClassWithObject:(id)object className:(NSString *)className;
|
||||
|
||||
+ (void)registerClass:(Class)cla;
|
||||
|
||||
+ (BOOL)setObject:(id)object toClass:(Class)cla;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e6c0a59660b31f419175804f218c192
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
#import "TDClassHelper.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation TDClassHelper
|
||||
|
||||
+ (Class _Nullable)allocateClassWithObject:(id)object className:(NSString *)className {
|
||||
if (!object || className.length <= 0) {
|
||||
return nil;
|
||||
}
|
||||
Class originalClass = object_getClass(object);
|
||||
Class subclass = NSClassFromString(className);
|
||||
if (subclass) {
|
||||
return nil;
|
||||
}
|
||||
subclass = objc_allocateClassPair(originalClass, className.UTF8String, 0);
|
||||
if (class_getInstanceSize(originalClass) != class_getInstanceSize(subclass)) {
|
||||
return nil;
|
||||
}
|
||||
return subclass;
|
||||
}
|
||||
|
||||
+ (void)registerClass:(Class)cla {
|
||||
if (cla) {
|
||||
objc_registerClassPair(cla);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)setObject:(id)object toClass:(Class)cla {
|
||||
if (cla && object && object_getClass(object) != cla) {
|
||||
return object_setClass(object, cla);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 420d8a217a542bf4981ba78f9aed757c
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TDHookDelegateProtocol <NSObject>
|
||||
@optional
|
||||
+ (NSSet<NSString *> *)optionalSelectors;
|
||||
|
||||
@end
|
||||
|
||||
@interface TDDelegateProxy : NSObject <TDHookDelegateProtocol>
|
||||
|
||||
+ (void)proxyDelegate:(id)delegate selectors:(NSSet<NSString *>*)selectors;
|
||||
|
||||
+ (void)invokeWithTarget:(NSObject *)target selector:(SEL)selector, ...;
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2;
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 arg3:(id)arg3;
|
||||
|
||||
+ (void)resolveOptionalSelectorsForDelegate:(id)delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4777c08c43760724ab9649b8c1a1752d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
#import "TDDelegateProxy.h"
|
||||
#import "TDClassHelper.h"
|
||||
#import "TDMethodHelper.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
static NSString * const kTDNSObjectRemoveObserverSelector = @"removeObserver:forKeyPath:";
|
||||
static NSString * const kTDNSObjectAddObserverSelector = @"addObserver:forKeyPath:options:context:";
|
||||
static NSString * const kTDNSObjectClassSelector = @"class";
|
||||
|
||||
@implementation TDDelegateProxy
|
||||
|
||||
+ (void)proxyDelegate:(id)delegate selectors:(NSSet<NSString *> *)selectors {
|
||||
if (object_isClass(delegate) || selectors.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class proxyClass = [self class];
|
||||
NSMutableSet *delegateSelectors = [NSMutableSet setWithSet:selectors];
|
||||
|
||||
TDDelegateProxyObject *object = [delegate thinkingdata_delegateObject];
|
||||
if (!object) {
|
||||
object = [[TDDelegateProxyObject alloc] initWithDelegate:delegate proxy:proxyClass];
|
||||
[delegate setThinkingdata_delegateObject:object];
|
||||
}
|
||||
|
||||
[delegateSelectors minusSet:object.selectors];
|
||||
if (delegateSelectors.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (object.thinkingClass) {
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.thinkingClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
|
||||
if (![object_getClass(delegate) isSubclassOfClass:object.thinkingClass]) {
|
||||
[TDClassHelper setObject:delegate toClass:object.thinkingClass];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (object.kvoClass) {
|
||||
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectRemoveObserverSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectRemoveObserverSelector];
|
||||
}
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.kvoClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
return;
|
||||
}
|
||||
|
||||
Class thinkingClass = [TDClassHelper allocateClassWithObject:delegate className:object.thinkingClassName];
|
||||
[TDClassHelper registerClass:thinkingClass];
|
||||
|
||||
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectAddObserverSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectAddObserverSelector];
|
||||
}
|
||||
|
||||
if (![object.selectors containsObject:kTDNSObjectClassSelector]) {
|
||||
[delegateSelectors addObject:kTDNSObjectClassSelector];
|
||||
}
|
||||
|
||||
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:thinkingClass];
|
||||
[object.selectors unionSet:delegateSelectors];
|
||||
|
||||
[TDClassHelper setObject:delegate toClass:thinkingClass];
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithSelectors:(NSSet<NSString *> *)selectors fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
for (NSString *selector in selectors) {
|
||||
SEL sel = NSSelectorFromString(selector);
|
||||
[TDMethodHelper addInstanceMethodWithSelector:sel fromClass:fromClass toClass:toClass];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 arg3:(id)arg3 {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
BOOL returnValue = NO;
|
||||
@try {
|
||||
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2, arg3);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
BOOL returnValue = NO;
|
||||
@try {
|
||||
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
+ (void)invokeWithTarget:(NSObject *)target selector:(SEL)selector, ... {
|
||||
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
||||
|
||||
va_list args;
|
||||
va_start(args, selector);
|
||||
id arg1 = nil, arg2 = nil, arg3 = nil, arg4 = nil;
|
||||
NSInteger count = [NSStringFromSelector(selector) componentsSeparatedByString:@":"].count - 1;
|
||||
for (NSInteger i = 0; i < count; i++) {
|
||||
i == 0 ? (arg1 = va_arg(args, id)) : nil;
|
||||
i == 1 ? (arg2 = va_arg(args, id)) : nil;
|
||||
i == 2 ? (arg3 = va_arg(args, id)) : nil;
|
||||
i == 3 ? (arg4 = va_arg(args, id)) : nil;
|
||||
}
|
||||
struct objc_super targetSuper = {
|
||||
.receiver = target,
|
||||
.super_class = originalClass
|
||||
};
|
||||
|
||||
@try {
|
||||
void (*func)(struct objc_super *, SEL, id, id, id, id) = (void *)&objc_msgSendSuper;
|
||||
func(&targetSuper, selector, arg1, arg2, arg3, arg4);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"msgSendSuper with exception: %@", exception);
|
||||
} @finally {
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void)resolveOptionalSelectorsForDelegate:(id)delegate {
|
||||
if (object_isClass(delegate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSSet *currentOptionalSelectors = ((NSObject *)delegate).thinkingdata_optionalSelectors;
|
||||
NSMutableSet *optionalSelectors = [[NSMutableSet alloc] init];
|
||||
if (currentOptionalSelectors) {
|
||||
[optionalSelectors unionSet:currentOptionalSelectors];
|
||||
}
|
||||
|
||||
if ([self respondsToSelector:@selector(optionalSelectors)] &&[self optionalSelectors]) {
|
||||
[optionalSelectors unionSet:[self optionalSelectors]];
|
||||
}
|
||||
((NSObject *)delegate).thinkingdata_optionalSelectors = [optionalSelectors copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Class
|
||||
@implementation TDDelegateProxy (Class)
|
||||
|
||||
- (Class)class {
|
||||
if (self.thinkingdata_delegateObject.delegateClass) {
|
||||
return self.thinkingdata_delegateObject.delegateClass;
|
||||
}
|
||||
return [super class];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - KVO
|
||||
@implementation TDDelegateProxy (KVO)
|
||||
|
||||
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
|
||||
[super addObserver:observer forKeyPath:keyPath options:options context:context];
|
||||
if (self.thinkingdata_delegateObject) {
|
||||
[TDMethodHelper replaceInstanceMethodWithDestinationSelector:@selector(class) sourceSelector:@selector(class) fromClass:TDDelegateProxy.class toClass:object_getClass(self)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
|
||||
BOOL oldClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
||||
[super removeObserver:observer forKeyPath:keyPath];
|
||||
BOOL newClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
||||
|
||||
if (oldClassIsKVO && !newClassIsKVO) {
|
||||
Class delegateProxy = self.thinkingdata_delegateObject.delegateProxy;
|
||||
NSSet *selectors = [self.thinkingdata_delegateObject.selectors copy];
|
||||
|
||||
[self.thinkingdata_delegateObject removeKVO];
|
||||
if ([delegateProxy respondsToSelector:@selector(proxyDelegate:selectors:)]) {
|
||||
[delegateProxy proxyDelegate:self selectors:selectors];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 315c41452d35ed64aa832c93b52b2b3a
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDDelegateProxyObject : NSObject
|
||||
|
||||
@property (nonatomic, strong) Class delegateISA;
|
||||
|
||||
@property (nonatomic, strong, nullable) Class kvoClass;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *thinkingClassName;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) Class thinkingClass;
|
||||
|
||||
@property (nonatomic, strong) id delegateClass;
|
||||
|
||||
@property (nonatomic, strong) Class delegateProxy;
|
||||
|
||||
@property (nonatomic, strong) NSMutableSet *selectors;
|
||||
|
||||
- (instancetype)initWithDelegate:(id)delegate proxy:(id)proxy;
|
||||
|
||||
- (void)removeKVO;
|
||||
|
||||
@end
|
||||
|
||||
@interface TDDelegateProxyObject (Utils)
|
||||
|
||||
+ (BOOL)isKVOClass:(Class _Nullable)cls;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85ad8b65ff9356a47a3a7ae39c279ddb
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
#import "TDDelegateProxyObject.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
NSString * const kTDDelegateClassThinkingSuffix = @"_TD.THINKINGDATA";
|
||||
NSString * const kTDDelegateClassKVOPrefix = @"KVONotifying_";
|
||||
|
||||
@implementation TDDelegateProxyObject
|
||||
|
||||
- (instancetype)initWithDelegate:(id)delegate proxy:(id)proxy {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_delegateProxy = proxy;
|
||||
|
||||
_selectors = [NSMutableSet set];
|
||||
_delegateClass = [delegate class];
|
||||
|
||||
Class cla = object_getClass(delegate);
|
||||
NSString *name = NSStringFromClass(cla);
|
||||
|
||||
if ([name containsString:kTDDelegateClassKVOPrefix]) {
|
||||
_delegateISA = class_getSuperclass(cla);
|
||||
_kvoClass = cla;
|
||||
} else if ([name containsString:kTDDelegateClassThinkingSuffix]) {
|
||||
_delegateISA = class_getSuperclass(cla);
|
||||
_thinkingClassName = name;
|
||||
} else {
|
||||
_delegateISA = cla;
|
||||
_thinkingClassName = [NSString stringWithFormat:@"%@%@", name, kTDDelegateClassThinkingSuffix];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (Class)thinkingClass {
|
||||
return NSClassFromString(self.thinkingClassName);
|
||||
}
|
||||
|
||||
- (void)removeKVO {
|
||||
self.kvoClass = nil;
|
||||
self.thinkingClassName = [NSString stringWithFormat:@"%@%@", self.delegateISA, kTDDelegateClassThinkingSuffix];
|
||||
[self.selectors removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Utils
|
||||
|
||||
@implementation TDDelegateProxyObject (Utils)
|
||||
|
||||
+ (BOOL)isKVOClass:(Class _Nullable)cls {
|
||||
return [NSStringFromClass(cls) containsString:kTDDelegateClassKVOPrefix];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc0971c2929b9c34095463469ddf31cd
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TDMethodHelper : NSObject
|
||||
|
||||
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass;
|
||||
|
||||
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (void)addClassMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
+ (IMP _Nullable)replaceInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass;
|
||||
|
||||
/// swizzle respondsToSelector 方法
|
||||
/// 用于处理未实现代理方法也能采集事件的逻辑
|
||||
+ (void)swizzleRespondsToSelector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b944f39f6447d9459b90a9b3dcf34e8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
#import "TDMethodHelper.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "TDNewSwizzle.h"
|
||||
#import "NSObject+TDDelegateProxy.h"
|
||||
|
||||
@implementation TDMethodHelper
|
||||
|
||||
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
|
||||
Method aMethod = class_getInstanceMethod(aClass, selector);
|
||||
return method_getImplementation(aMethod);
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
[self addInstanceMethodWithDestinationSelector:methodSelector sourceSelector:methodSelector fromClass:fromClass toClass:toClass];
|
||||
}
|
||||
|
||||
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getInstanceMethod(fromClass, sourceSelector);
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
|
||||
IMP destinationIMP = [self implementationOfMethodSelector:destinationSelector fromClass:toClass];
|
||||
if (destinationIMP == methodIMP) {
|
||||
return;
|
||||
}
|
||||
class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)addClassMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getClassMethod(fromClass, sourceSelector);
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
|
||||
class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
}
|
||||
|
||||
+ (IMP _Nullable)replaceInstanceMethodWithDestinationSelector:(SEL)destinationSelector sourceSelector:(SEL)sourceSelector fromClass:(Class)fromClass toClass:(Class)toClass {
|
||||
Method method = class_getInstanceMethod(fromClass, sourceSelector);
|
||||
IMP methodIMP = method_getImplementation(method);
|
||||
const char *types = method_getTypeEncoding(method);
|
||||
return class_replaceMethod(toClass, destinationSelector, methodIMP, types);
|
||||
}
|
||||
|
||||
+ (void)swizzleRespondsToSelector {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[NSObject td_new_swizzleMethod:@selector(respondsToSelector:)
|
||||
withMethod:@selector(thinkingdata_respondsToSelector:)
|
||||
error:NULL];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b2861556d1c5c44eac95b8110fb3481
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c523c7ccc8260b4296c4f5e308de62a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (TDNewSwizzle)
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
+ (BOOL)td_new_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withClass:(Class)altCla_ withMethod:(SEL)altSel_ error:(NSError **)error_;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f13e46f9ceae76041a668ce21ee63c12
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
#import "TDNewSwizzle.h"
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
#else
|
||||
#import <objc/objc-class.h>
|
||||
#endif
|
||||
|
||||
#define TDSetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
|
||||
if (ERROR_VAR) { \
|
||||
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
|
||||
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
|
||||
code:-1 \
|
||||
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
|
||||
}
|
||||
#define TDSetNSError(ERROR_VAR, FORMAT,...) TDSetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
|
||||
|
||||
#if OBJC_API_VERSION >= 2
|
||||
#define TDGetClass(obj) object_getClass(obj)
|
||||
#else
|
||||
#define TDGetClass(obj) (obj ? obj->isa : Nil)
|
||||
#endif
|
||||
|
||||
@implementation NSObject (TDNewSwizzle)
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withClass:(Class)altCla_ withMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
Method origMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!origMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
Method altMethod = class_getInstanceMethod(altCla_, altSel_);
|
||||
if (!altMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [altCla_ class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
class_addMethod(self,
|
||||
origSel_,
|
||||
class_getMethodImplementation(self, origSel_),
|
||||
method_getTypeEncoding(origMethod));
|
||||
class_addMethod(altCla_,
|
||||
altSel_,
|
||||
class_getMethodImplementation(altCla_, altSel_),
|
||||
method_getTypeEncoding(altMethod));
|
||||
|
||||
//交换之前,先对自定义方法进行添加
|
||||
BOOL didAddMethod = class_addMethod(self,
|
||||
altSel_,
|
||||
method_getImplementation(altMethod),
|
||||
method_getTypeEncoding(altMethod));
|
||||
if (didAddMethod) {
|
||||
method_exchangeImplementations(origMethod, class_getInstanceMethod(self, altSel_));
|
||||
}
|
||||
return didAddMethod;
|
||||
}
|
||||
|
||||
+ (BOOL)td_new_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
#if OBJC_API_VERSION >= 2
|
||||
Method origMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!origMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
Method altMethod = class_getInstanceMethod(self, altSel_);
|
||||
if (!altMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
class_addMethod(self,
|
||||
origSel_,
|
||||
class_getMethodImplementation(self, origSel_),
|
||||
method_getTypeEncoding(origMethod));
|
||||
class_addMethod(self,
|
||||
altSel_,
|
||||
class_getMethodImplementation(self, altSel_),
|
||||
method_getTypeEncoding(altMethod));
|
||||
|
||||
method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
|
||||
return YES;
|
||||
#else
|
||||
// Scan for non-inherited methods.
|
||||
Method directOriginalMethod = NULL, directAlternateMethod = NULL;
|
||||
|
||||
void *iterator = NULL;
|
||||
struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
|
||||
while (mlist) {
|
||||
int method_index = 0;
|
||||
for (; method_index < mlist->method_count; method_index++) {
|
||||
if (mlist->method_list[method_index].method_name == origSel_) {
|
||||
assert(!directOriginalMethod);
|
||||
directOriginalMethod = &mlist->method_list[method_index];
|
||||
}
|
||||
if (mlist->method_list[method_index].method_name == altSel_) {
|
||||
assert(!directAlternateMethod);
|
||||
directAlternateMethod = &mlist->method_list[method_index];
|
||||
}
|
||||
}
|
||||
mlist = class_nextMethodList(self, &iterator);
|
||||
}
|
||||
|
||||
// If either method is inherited, copy it up to the target class to make it non-inherited.
|
||||
if (!directOriginalMethod || !directAlternateMethod) {
|
||||
Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
|
||||
if (!directOriginalMethod) {
|
||||
inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
|
||||
if (!inheritedOriginalMethod) {
|
||||
TDSetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
if (!directAlternateMethod) {
|
||||
inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
|
||||
if (!inheritedAlternateMethod) {
|
||||
TDSetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
|
||||
struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
|
||||
hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
|
||||
hoisted_method_list->method_count = hoisted_method_count;
|
||||
Method hoisted_method = hoisted_method_list->method_list;
|
||||
|
||||
if (!directOriginalMethod) {
|
||||
bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
|
||||
directOriginalMethod = hoisted_method++;
|
||||
}
|
||||
if (!directAlternateMethod) {
|
||||
bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
|
||||
directAlternateMethod = hoisted_method;
|
||||
}
|
||||
class_addMethods(self, hoisted_method_list);
|
||||
}
|
||||
|
||||
// Swizzle.
|
||||
IMP temp = directOriginalMethod->method_imp;
|
||||
directOriginalMethod->method_imp = directAlternateMethod->method_imp;
|
||||
directAlternateMethod->method_imp = temp;
|
||||
|
||||
return YES;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (BOOL)td_new_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError **)error_ {
|
||||
return [TDGetClass((id)self) td_new_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83e5e45c60e92754d917ed33e60f7552
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TDAppLaunchReason.h
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/11/17.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSDictionary *appPushClickDic;
|
||||
|
||||
@interface TDAppLaunchReason : NSObject
|
||||
|
||||
@property(nonatomic, copy) NSDictionary *appLaunchParams;
|
||||
|
||||
+ (TDAppLaunchReason *)sharedInstance;
|
||||
|
||||
- (void)clearAppLaunchParams;
|
||||
|
||||
+ (void)td_ops_push_click:(NSDictionary *)userInfo;
|
||||
|
||||
+ (NSDictionary*)getAppPushDic;
|
||||
|
||||
+ (void)clearAppPushParams;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 265a13baa4b98574a93342705cae65eb
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// TDAppLaunchReason.m
|
||||
// ThinkingSDK
|
||||
//
|
||||
// Created by wwango on 2021/11/17.
|
||||
// Copyright © 2021 thinkingdata. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TDAppLaunchReason.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "TDCommonUtil.h"
|
||||
#import "TDPresetProperties+TDDisProperties.h"
|
||||
#import "TDAppState.h"
|
||||
#import "ThinkingAnalyticsSDKPrivate.h"
|
||||
#import "TDAppDelegateProxyManager.h"
|
||||
#import "TAPushClickEvent.h"
|
||||
|
||||
@implementation TDAppLaunchReason
|
||||
|
||||
|
||||
+ (void)load {
|
||||
|
||||
[TDPresetProperties disPresetProperties];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:[TDAppLaunchReason sharedInstance] selector:@selector(_applicationDidFinishLaunchingNotification:) name:UIApplicationDidFinishLaunchingNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:[TDAppLaunchReason sharedInstance] selector:@selector(_applicationDidEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
}
|
||||
|
||||
+ (void)td_ops_push_click:(NSDictionary *)userInfo {
|
||||
|
||||
@try {
|
||||
if ([userInfo.allKeys containsObject:@"te_extras"] && [userInfo[@"te_extras"] isKindOfClass:[NSString class]]) {
|
||||
NSData *jsonData = [userInfo[@"te_extras"] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *err;
|
||||
NSDictionary *responseDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
NSDictionary *opsReceiptProperties = responseDic[@"#ops_receipt_properties"];
|
||||
if ([opsReceiptProperties isKindOfClass:[NSString class]]) {
|
||||
NSString *opsStr = (NSString *)opsReceiptProperties;
|
||||
opsReceiptProperties = [NSJSONSerialization JSONObjectWithData:[opsStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&err];
|
||||
}
|
||||
if (opsReceiptProperties && [opsReceiptProperties isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary *dic = [ThinkingAnalyticsSDK _getAllInstances];
|
||||
if(dic == nil || dic.count == 0){
|
||||
appPushClickDic = opsReceiptProperties;
|
||||
}else{
|
||||
for (NSString *instanceToken in dic.allKeys) {
|
||||
ThinkingAnalyticsSDK *instance = dic[instanceToken];
|
||||
TAPushClickEvent *pushEvent = [[TAPushClickEvent alloc]initWithName: @"ops_push_click"];
|
||||
pushEvent.ops = opsReceiptProperties;
|
||||
[instance autoTrackWithEvent:pushEvent properties:@{}];
|
||||
[instance flush];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSDictionary *)getAppPushDic{
|
||||
return appPushClickDic;
|
||||
}
|
||||
|
||||
+ (void)clearAppPushParams{
|
||||
appPushClickDic = nil;
|
||||
}
|
||||
|
||||
+ (TDAppLaunchReason *)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static TDAppLaunchReason *appLaunchManager;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
appLaunchManager = [TDAppLaunchReason new];
|
||||
});
|
||||
return appLaunchManager;
|
||||
}
|
||||
|
||||
- (void)clearAppLaunchParams {
|
||||
self.appLaunchParams = @{@"url":@"",
|
||||
@"data":@{}};
|
||||
}
|
||||
|
||||
- (void)_applicationDidEnterBackgroundNotification:(NSNotification *)notification {
|
||||
[self clearAppLaunchParams];
|
||||
}
|
||||
|
||||
// 拦截冷启动和热启动的参数
|
||||
- (void)_applicationDidFinishLaunchingNotification:(NSNotification *)notification {
|
||||
|
||||
__weak TDAppLaunchReason *weakSelf = self;
|
||||
|
||||
NSDictionary *launchOptions = notification.userInfo;
|
||||
NSString *url = [self getInitDeeplink:launchOptions];
|
||||
NSDictionary *data = [self getInitData:launchOptions];
|
||||
|
||||
// 发送推送事件
|
||||
if (![TDPresetProperties disableOpsReceiptProperties] && launchOptions) {
|
||||
NSDictionary *remoteNotification = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
|
||||
[TDAppLaunchReason td_ops_push_click:remoteNotification];
|
||||
}
|
||||
|
||||
// 记录冷启动启动原因
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
|
||||
if (!launchOptions) {
|
||||
[weakSelf clearAppLaunchParams];
|
||||
} else if ([url isKindOfClass:[NSString class]] && url.length) {
|
||||
self.appLaunchParams = @{@"url": [TDCommonUtil string:url],
|
||||
@"data": @{}};
|
||||
} else {
|
||||
self.appLaunchParams = @{@"url": @"",
|
||||
@"data": [TDCommonUtil dictionary:data]};
|
||||
}
|
||||
}
|
||||
|
||||
UIApplication *application = [TDAppState sharedApplication];
|
||||
id applicationDelegate = [application delegate];
|
||||
if (applicationDelegate == nil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableStartReason]) {
|
||||
[[TDAppDelegateProxyManager defaultManager] proxyNotifications];
|
||||
}
|
||||
|
||||
if (![TDPresetProperties disableOpsReceiptProperties]) {
|
||||
[[TDAppDelegateProxyManager defaultManager] proxyNotifications];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getInitDeeplink:(NSDictionary *)launchOptions {
|
||||
|
||||
if (!launchOptions || ![launchOptions isKindOfClass:[NSDictionary class]]) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
if ([launchOptions isKindOfClass:[NSDictionary class]] &&
|
||||
[launchOptions.allKeys containsObject:UIApplicationLaunchOptionsURLKey]) {
|
||||
|
||||
return launchOptions[UIApplicationLaunchOptionsURLKey];
|
||||
|
||||
} else if ([launchOptions isKindOfClass:[NSDictionary class]] &&
|
||||
[launchOptions.allKeys containsObject:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
|
||||
|
||||
NSDictionary *userActivityDictionary = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
|
||||
NSString *type = userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey];
|
||||
if ([type isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
NSUserActivity *userActivity = userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"];
|
||||
return userActivity.webpageURL.absoluteString;
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSDictionary *)getInitData:(NSDictionary *)launchOptions {
|
||||
|
||||
if (!launchOptions || ![launchOptions isKindOfClass:[NSDictionary class]]) {
|
||||
return @{};
|
||||
}
|
||||
|
||||
if ([launchOptions.allKeys containsObject:UIApplicationLaunchOptionsLocalNotificationKey]) {
|
||||
// 本地推送可能会解析不出alertbody,这里特殊处理一下
|
||||
UILocalNotification *notification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
|
||||
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
|
||||
properties[@"alertBody"] = notification.alertBody;
|
||||
if (@available(iOS 8.2, *)) {
|
||||
properties[@"alertTitle"] = notification.alertTitle;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
return launchOptions;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 504f113ee087c6c40bbe3ab24800661c
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user