795 lines
32 KiB
Mathematica
795 lines
32 KiB
Mathematica
|
|
#import "TDAutoTrackManager.h"
|
||
|
|
|
||
|
|
#import "UIViewController+AutoTrack.h"
|
||
|
|
#import "UIApplication+AutoTrack.h"
|
||
|
|
#import "ThinkingAnalyticsSDKPrivate.h"
|
||
|
|
#import "TDPublicConfig.h"
|
||
|
|
#import "TDAutoClickEvent.h"
|
||
|
|
#import "TDAutoPageViewEvent.h"
|
||
|
|
#import "TDAppLifeCycle.h"
|
||
|
|
#import "TDAppState.h"
|
||
|
|
#import "TDRunTime.h"
|
||
|
|
#import "TDAppStartEvent.h"
|
||
|
|
#import "TDAppEndEvent.h"
|
||
|
|
#import "TDAppEndTracker.h"
|
||
|
|
#import "TDColdStartTracker.h"
|
||
|
|
#import "TDInstallTracker.h"
|
||
|
|
#import "TDAppState.h"
|
||
|
|
|
||
|
|
#if __has_include(<ThinkingDataCore/TDJSONUtil.h>)
|
||
|
|
#import <ThinkingDataCore/TDJSONUtil.h>
|
||
|
|
#else
|
||
|
|
#import "TDJSONUtil.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if __has_include(<ThinkingDataCore/NSObject+TDSwizzle.h>)
|
||
|
|
#import <ThinkingDataCore/NSObject+TDSwizzle.h>
|
||
|
|
#else
|
||
|
|
#import "NSObject+TDSwizzle.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if __has_include(<ThinkingDataCore/TDSwizzler.h>)
|
||
|
|
#import <ThinkingDataCore/TDSwizzler.h>
|
||
|
|
#else
|
||
|
|
#import "TDSwizzler.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if __has_include(<ThinkingDataCore/TDCorePresetDisableConfig.h>)
|
||
|
|
#import <ThinkingDataCore/TDCorePresetDisableConfig.h>
|
||
|
|
#else
|
||
|
|
#import "TDCorePresetDisableConfig.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#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 *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];
|
||
|
|
|
||
|
|
[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];
|
||
|
|
|
||
|
|
[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];
|
||
|
|
|
||
|
|
[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) {
|
||
|
|
|
||
|
|
TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||
|
|
|
||
|
|
if (type & ThinkingAnalyticsEventTypeAppClick) {
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid];
|
||
|
|
NSMutableDictionary *trackProperties = [properties mutableCopy];
|
||
|
|
|
||
|
|
NSMutableDictionary *finalProperties = [customProperties mutableCopy];
|
||
|
|
|
||
|
|
if ([instance innerIsViewTypeIgnored:[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];
|
||
|
|
|
||
|
|
[finalProperties addEntriesFromDictionary:properties];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (propertyWithAppid) {
|
||
|
|
NSDictionary *autoTrackproperties = [propertyWithAppid objectForKey:appid];
|
||
|
|
if ([autoTrackproperties isKindOfClass:[NSDictionary class]]) {
|
||
|
|
[trackProperties addEntriesFromDictionary:autoTrackproperties];
|
||
|
|
|
||
|
|
[finalProperties addEntriesFromDictionary:autoTrackproperties];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TDAutoClickEvent *clickEvent = [[TDAutoClickEvent alloc] initWithName:TD_APP_CLICK_EVENT];
|
||
|
|
clickEvent.time = trackDate;
|
||
|
|
clickEvent.elementId = elementId;
|
||
|
|
clickEvent.elementType = elementType;
|
||
|
|
clickEvent.elementContent = elementContent;
|
||
|
|
clickEvent.elementPosition = elementPosition;
|
||
|
|
clickEvent.pageTitle = elementPageTitle;
|
||
|
|
clickEvent.screenName = elementScreenName;
|
||
|
|
|
||
|
|
[instance autoTrackWithEvent:clickEvent properties:finalProperties];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)trackWithAppid:(NSString *)appid withOption:(TDAutoTrackEventType)type {
|
||
|
|
TD_LOCK(self.trackOptionLock);
|
||
|
|
self.autoTrackOptions[appid] = @(type);
|
||
|
|
TD_UNLOCK(self.trackOptionLock);
|
||
|
|
|
||
|
|
if (type & ThinkingAnalyticsEventTypeAppClick || type & ThinkingAnalyticsEventTypeAppViewScreen) {
|
||
|
|
[self swizzleVC];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type & ThinkingAnalyticsEventTypeAppInstall) {
|
||
|
|
TDAutoTrackEvent *event = [[TDAutoTrackEvent 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 instanceWithAppid:appid];
|
||
|
|
[instance innerTimeEvent: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;
|
||
|
|
TDAppStartEvent *event = [[TDAppStartEvent alloc] initWithName:eventName];
|
||
|
|
event.resumeFromBackground = NO;
|
||
|
|
if (![TDCorePresetDisableConfig 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) {
|
||
|
|
[ThinkingExceptionHandler start];
|
||
|
|
}
|
||
|
|
|
||
|
|
TDLogInfo(@"enable auto track: %li", type);
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)trackWithEvent:(TDAutoTrackEvent *)event withProperties:(NSDictionary *)properties {
|
||
|
|
for (NSString *appid in self.autoTrackOptions.allKeys) {
|
||
|
|
TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||
|
|
if (type & event.autoTrackEventType) {
|
||
|
|
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid];
|
||
|
|
[instance autoTrackWithEvent:event properties:properties];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (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:(TDAutoTrackEventType)eventType {
|
||
|
|
BOOL isIgnored = YES;
|
||
|
|
for (NSString *appid in self.autoTrackOptions) {
|
||
|
|
TDAutoTrackEventType type = (TDAutoTrackEventType)[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 *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];
|
||
|
|
|
||
|
|
[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) {
|
||
|
|
TDAutoTrackEventType type = [self.autoTrackOptions[appid] integerValue];
|
||
|
|
if (type & ThinkingAnalyticsEventTypeAppViewScreen) {
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid];
|
||
|
|
NSMutableDictionary *trackProperties = [properties mutableCopy];
|
||
|
|
|
||
|
|
NSMutableDictionary *finalProperties = [customProperties mutableCopy];
|
||
|
|
|
||
|
|
if ([instance isViewControllerIgnored:controller]
|
||
|
|
|| [instance innerIsViewTypeIgnored:[controller class]]) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (autoTrackerAppidDic && [autoTrackerAppidDic objectForKey:appid]) {
|
||
|
|
NSDictionary *dic = [autoTrackerAppidDic objectForKey:appid];
|
||
|
|
if ([dic isKindOfClass:[NSDictionary class]]) {
|
||
|
|
[trackProperties addEntriesFromDictionary:dic];
|
||
|
|
|
||
|
|
[finalProperties addEntriesFromDictionary:dic];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (screenAutoTrackerAppidDic && [screenAutoTrackerAppidDic objectForKey:appid]) {
|
||
|
|
NSString *screenUrl = [screenAutoTrackerAppidDic objectForKey:appid];
|
||
|
|
[trackProperties setValue:screenUrl forKey:TD_EVENT_PROPERTY_URL_PROPERTY];
|
||
|
|
|
||
|
|
pageUrl = screenUrl;
|
||
|
|
}
|
||
|
|
|
||
|
|
TDAutoPageViewEvent *pageEvent = [[TDAutoPageViewEvent 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:finalProperties];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (BOOL)shouldTrackViewContrller:(Class)aClass {
|
||
|
|
return ![TDPublicConfig.controllers containsObject:NSStringFromClass(aClass)];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (TDAutoTrackEventType)autoTrackOptionForAppid:(NSString *)appid {
|
||
|
|
return (TDAutoTrackEventType)[[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:kTDAppLifeCycleStateWillChangeNotification object:nil];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)appStateWillChangeNotification:(NSNotification *)notification {
|
||
|
|
TDAppLifeCycleState newState = [[notification.userInfo objectForKey:kTDAppLifeCycleNewStateKey] integerValue];
|
||
|
|
TDAppLifeCycleState oldState = [[notification.userInfo objectForKey:kTDAppLifeCycleOldStateKey] integerValue];
|
||
|
|
|
||
|
|
if (newState == TDAppLifeCycleStateStart) {
|
||
|
|
for (NSString *appid in self.autoTrackOptions.allKeys) {
|
||
|
|
TDAutoTrackEventType type = (TDAutoTrackEventType)[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 != TDAppLifeCycleStateInit) {
|
||
|
|
NSString *eventName = [TDAppState shareInstance].relaunchInBackground ? TD_APP_START_BACKGROUND_EVENT : TD_APP_START_EVENT;
|
||
|
|
TDAppStartEvent *event = [[TDAppStartEvent alloc] initWithName:eventName];
|
||
|
|
event.resumeFromBackground = YES;
|
||
|
|
|
||
|
|
if (![TDCorePresetDisableConfig 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 instanceWithAppid:appid];
|
||
|
|
[instance innerTimeEvent:TD_APP_END_EVENT];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (newState == TDAppLifeCycleStateEnd) {
|
||
|
|
for (NSString *appid in self.autoTrackOptions) {
|
||
|
|
TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue];
|
||
|
|
if (type & ThinkingAnalyticsEventTypeAppEnd) {
|
||
|
|
TDAppEndEvent *event = [[TDAppEndEvent 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 instanceWithAppid:appid];
|
||
|
|
[instance innerTimeEvent: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
|