From 6674b8599b8bd56704c4317fd8d529442689809f Mon Sep 17 00:00:00 2001 From: David Whetstone Date: Mon, 15 Jul 2019 22:03:38 -0700 Subject: [PATCH 01/11] Update Nimble to build with Xcode 10.2 --- Analytics.xcodeproj/project.pbxproj | 4 ++-- Podfile.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj index c40b6a9a3..0d58c3dbe 100644 --- a/Analytics.xcodeproj/project.pbxproj +++ b/Analytics.xcodeproj/project.pbxproj @@ -541,7 +541,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Alamofire-Synchronous/Alamofire_Synchronous.framework", "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", @@ -560,7 +560,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Podfile.lock b/Podfile.lock index 162d71864..81cf77d3d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,7 +2,7 @@ PODS: - Alamofire (4.6.0) - Alamofire-Synchronous (4.0.0): - Alamofire (~> 4.0) - - Nimble (7.3.1) + - Nimble (7.3.4) - Nocilla (0.11.0) - Quick (1.2.0) - SwiftTryCatch (1.0.0) @@ -35,11 +35,11 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: f41a599bd63041760b26d393ec1069d9d7b917f4 Alamofire-Synchronous: eedf1e6e961c3795a63c74990b3f7d9fbfac7e50 - Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae + Nimble: 051e3d8912d40138fa5591c78594f95fb172af37 Nocilla: 7af7a386071150cc8aa5da4da97d060f049dd61c Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 SwiftTryCatch: 2f4ef36cf5396bdb450006b70633dbce5260d3b3 PODFILE CHECKSUM: cf4abb4263c7b514d71c70514284ac657d90865d -COCOAPODS: 1.5.3 +COCOAPODS: 1.7.4 From 491eb350be42851a65b1fe1bc28c28ce156db566 Mon Sep 17 00:00:00 2001 From: David Whetstone Date: Tue, 24 Jul 2018 16:35:31 -0700 Subject: [PATCH 02/11] Add name and properties to auto screen reporting --- Analytics.xcodeproj/project.pbxproj | 4 ++++ .../Classes/Internal/SEGScreenReporting.h | 16 ++++++++++++++ .../Internal/UIViewController+SEGScreen.h | 3 ++- .../Internal/UIViewController+SEGScreen.m | 21 +++++++++++++++++-- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 Analytics/Classes/Internal/SEGScreenReporting.h diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj index 0d58c3dbe..45add5ff1 100644 --- a/Analytics.xcodeproj/project.pbxproj +++ b/Analytics.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3481C14AEC76E5A8DA64DA59 /* Pods_AnalyticsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89033CBF22319674E6CE7E61 /* Pods_AnalyticsTests.framework */; }; + 630FC8EA2107F2A500A759C5 /* SEGScreenReporting.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E265C791FB1178C0030E08E /* IntegrationsManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */; }; 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEC1C702017EA370089C478 /* EndToEndTests.swift */; }; EA88A5981DED7608009FB66A /* SEGSerializableValue.h in Headers */ = {isa = PBXBuildFile; fileRef = EA88A5971DED7608009FB66A /* SEGSerializableValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -89,6 +90,7 @@ /* Begin PBXFileReference section */ 1DF03A6929EEFE7158913224 /* Pods-AnalyticsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AnalyticsTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests.debug.xcconfig"; sourceTree = ""; }; + 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SEGScreenReporting.h; sourceTree = ""; }; 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsManagerTest.swift; sourceTree = ""; }; 6EEC1C702017EA370089C478 /* EndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndToEndTests.swift; sourceTree = ""; }; 89033CBF22319674E6CE7E61 /* Pods_AnalyticsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AnalyticsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -346,6 +348,7 @@ EADEB8A11DECD12B005322DA /* SEGUtils.m */, EADEB8A21DECD12B005322DA /* UIViewController+SEGScreen.h */, EADEB8A31DECD12B005322DA /* UIViewController+SEGScreen.m */, + 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */, ); path = Internal; sourceTree = ""; @@ -413,6 +416,7 @@ EADEB8601DECD080005322DA /* Analytics.h in Headers */, EADEB8C31DECD12B005322DA /* SEGAnalyticsUtils.h in Headers */, EADEB8B91DECD12B005322DA /* SEGIntegrationsManager.h in Headers */, + 630FC8EA2107F2A500A759C5 /* SEGScreenReporting.h in Headers */, EADEB8AE1DECD12B005322DA /* SEGAES256Crypto.h in Headers */, EADEB8C51DECD12B005322DA /* SEGFileStorage.h in Headers */, EAA5427D1EB42B8C00945DA7 /* SEGReachability.h in Headers */, diff --git a/Analytics/Classes/Internal/SEGScreenReporting.h b/Analytics/Classes/Internal/SEGScreenReporting.h new file mode 100644 index 000000000..edf924b97 --- /dev/null +++ b/Analytics/Classes/Internal/SEGScreenReporting.h @@ -0,0 +1,16 @@ +#import +#import "SEGSerializableValue.h" + +/** Implement this protocol to add properties to automatic screen reporting + */ + +NS_ASSUME_NONNULL_BEGIN + +@protocol SEGScreenReporting +@optional @property (readonly, nullable) NSString * seg_screenName; +@optional @property (readonly, nullable) SERIALIZABLE_DICT seg_screenProperties; +@end + +NS_ASSUME_NONNULL_END + + diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.h b/Analytics/Classes/Internal/UIViewController+SEGScreen.h index 4d762f08c..8be5cb080 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.h +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.h @@ -1,5 +1,5 @@ #import - +#import "SEGSerializableValue.h" @interface UIViewController (SEGScreen) @@ -7,3 +7,4 @@ + (UIViewController *)seg_topViewController; @end + diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.m b/Analytics/Classes/Internal/UIViewController+SEGScreen.m index 8aaaa5861..1862b7c8e 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.m +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.m @@ -2,6 +2,7 @@ #import #import "SEGAnalytics.h" #import "SEGAnalyticsUtils.h" +#import "SEGScreenReporting.h" @implementation UIViewController (SEGScreen) @@ -65,7 +66,22 @@ - (void)seg_viewDidAppear:(BOOL)animated return; } - NSString *name = [top title]; + SERIALIZABLE_DICT properties = nil; + NSString *name = nil; + + if ([top conformsToProtocol:@protocol(SEGScreenReporting)]) { + __auto_type screenReporting = (UIViewController*)top; + + if ([screenReporting respondsToSelector:@selector(seg_screenProperties)]) { + properties = screenReporting.seg_screenProperties; + } + + if ([top respondsToSelector:@selector(seg_screenName)]) { + name = screenReporting.seg_screenName; + } + } + + name = name ?: [top title]; if (!name || name.length == 0) { name = [[[top class] description] stringByReplacingOccurrencesOfString:@"ViewController" withString:@""]; // Class name could be just "ViewController". @@ -74,7 +90,8 @@ - (void)seg_viewDidAppear:(BOOL)animated name = @"Unknown"; } } - [[SEGAnalytics sharedAnalytics] screen:name properties:nil options:nil]; + + [[SEGAnalytics sharedAnalytics] screen:name properties:properties options:nil]; [self seg_viewDidAppear:animated]; } From 17706b6b682a59a4dfe3aef9bb602b05a5b96dc3 Mon Sep 17 00:00:00 2001 From: David Whetstone Date: Wed, 25 Jul 2018 11:16:59 -0700 Subject: [PATCH 03/11] Make seg_topViewController handle tab and custom container VCs --- .../Classes/Internal/SEGScreenReporting.h | 3 +- .../Internal/UIViewController+SEGScreen.m | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Analytics/Classes/Internal/SEGScreenReporting.h b/Analytics/Classes/Internal/SEGScreenReporting.h index edf924b97..16d9e45de 100644 --- a/Analytics/Classes/Internal/SEGScreenReporting.h +++ b/Analytics/Classes/Internal/SEGScreenReporting.h @@ -1,4 +1,4 @@ -#import +#import #import "SEGSerializableValue.h" /** Implement this protocol to add properties to automatic screen reporting @@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol SEGScreenReporting @optional @property (readonly, nullable) NSString * seg_screenName; @optional @property (readonly, nullable) SERIALIZABLE_DICT seg_screenProperties; +@optional @property (readonly, nullable) UIViewController *seg_mainViewController; @end NS_ASSUME_NONNULL_END diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.m b/Analytics/Classes/Internal/UIViewController+SEGScreen.m index 1862b7c8e..64ea1d6c5 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.m +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.m @@ -44,18 +44,50 @@ + (UIViewController *)seg_topViewController } + (UIViewController *)seg_topViewController:(UIViewController *)rootViewController +{ + UIViewController *nextRootViewController = [self seg_nextRootViewController:rootViewController]; + if (nextRootViewController) { + return [self seg_topViewController:nextRootViewController]; + } + + return rootViewController; +} + ++ (UIViewController *)seg_nextRootViewController:(UIViewController *)rootViewController { UIViewController *presentedViewController = rootViewController.presentedViewController; if (presentedViewController != nil) { - return [self seg_topViewController:presentedViewController]; + return presentedViewController; } if ([rootViewController isKindOfClass:[UINavigationController class]]) { - UIViewController *lastViewController = [[(UINavigationController *)rootViewController viewControllers] lastObject]; - return [self seg_topViewController:lastViewController]; + UIViewController *lastViewController = ((UINavigationController *)rootViewController).viewControllers.lastObject; + return lastViewController; } - return rootViewController; + if ([rootViewController isKindOfClass:[UITabBarController class]]) { + __auto_type *currentTabViewController = ((UITabBarController*)rootViewController).selectedViewController; + if (currentTabViewController != nil) { + return currentTabViewController; + } + } + + if (rootViewController.childViewControllers.count > 0) { + if ([rootViewController conformsToProtocol:@protocol(SEGScreenReporting)]) { + __auto_type screenReporting = (UIViewController*)rootViewController; + if ([screenReporting respondsToSelector:@selector(seg_mainViewController)]) { + return screenReporting.seg_mainViewController; + } + } + + // fall back on first child UIViewController as a "best guess" assumption + __auto_type *firstChildViewController = rootViewController.childViewControllers.firstObject; + if (firstChildViewController != nil) { + return firstChildViewController; + } + } + + return nil; } - (void)seg_viewDidAppear:(BOOL)animated From a323d5c983d469628de93ba27fec563b3790c62d Mon Sep 17 00:00:00 2001 From: David Whetstone Date: Mon, 10 Sep 2018 13:56:43 -0700 Subject: [PATCH 04/11] Simplify SEGScreenReporting protocol Inspired by comments from @f2prateek, simplify the the `SEGScreenReporting` protocol to replace the name and properties fields with a single method (`seg_trackScreen`) that can be implemented when screen tracking for a specific view controller that needs a custom name, properties, or options. --- .../Classes/Internal/SEGScreenReporting.h | 8 ++--- .../Internal/UIViewController+SEGScreen.m | 31 ++++++------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Analytics/Classes/Internal/SEGScreenReporting.h b/Analytics/Classes/Internal/SEGScreenReporting.h index 16d9e45de..f40a05887 100644 --- a/Analytics/Classes/Internal/SEGScreenReporting.h +++ b/Analytics/Classes/Internal/SEGScreenReporting.h @@ -1,15 +1,15 @@ #import #import "SEGSerializableValue.h" -/** Implement this protocol to add properties to automatic screen reporting +/** Implement this protocol to override automatic screen reporting */ NS_ASSUME_NONNULL_BEGIN @protocol SEGScreenReporting -@optional @property (readonly, nullable) NSString * seg_screenName; -@optional @property (readonly, nullable) SERIALIZABLE_DICT seg_screenProperties; -@optional @property (readonly, nullable) UIViewController *seg_mainViewController; +@optional +-(void) seg_trackScreen:(UIViewController*)screen name:(NSString*)name; +@property (readonly, nullable) UIViewController *seg_mainViewController; @end NS_ASSUME_NONNULL_END diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.m b/Analytics/Classes/Internal/UIViewController+SEGScreen.m index 64ea1d6c5..af0b2716d 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.m +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.m @@ -73,11 +73,9 @@ + (UIViewController *)seg_nextRootViewController:(UIViewController *)rootViewCon } if (rootViewController.childViewControllers.count > 0) { - if ([rootViewController conformsToProtocol:@protocol(SEGScreenReporting)]) { + if ([rootViewController conformsToProtocol:@protocol(SEGScreenReporting)] && [rootViewController respondsToSelector:@selector(seg_mainViewController)]) { __auto_type screenReporting = (UIViewController*)rootViewController; - if ([screenReporting respondsToSelector:@selector(seg_mainViewController)]) { - return screenReporting.seg_mainViewController; - } + return screenReporting.seg_mainViewController; } // fall back on first child UIViewController as a "best guess" assumption @@ -98,22 +96,7 @@ - (void)seg_viewDidAppear:(BOOL)animated return; } - SERIALIZABLE_DICT properties = nil; - NSString *name = nil; - - if ([top conformsToProtocol:@protocol(SEGScreenReporting)]) { - __auto_type screenReporting = (UIViewController*)top; - - if ([screenReporting respondsToSelector:@selector(seg_screenProperties)]) { - properties = screenReporting.seg_screenProperties; - } - - if ([top respondsToSelector:@selector(seg_screenName)]) { - name = screenReporting.seg_screenName; - } - } - - name = name ?: [top title]; + NSString *name = [top title]; if (!name || name.length == 0) { name = [[[top class] description] stringByReplacingOccurrencesOfString:@"ViewController" withString:@""]; // Class name could be just "ViewController". @@ -123,7 +106,13 @@ - (void)seg_viewDidAppear:(BOOL)animated } } - [[SEGAnalytics sharedAnalytics] screen:name properties:properties options:nil]; + if ([top conformsToProtocol:@protocol(SEGScreenReporting)] && [top respondsToSelector:@selector(seg_trackScreen)]) { + __auto_type screenReporting = (UIViewController*)top; + [screenReporting seg_trackScreen:top name:name]; + return; + } + + [[SEGAnalytics sharedAnalytics] screen:name properties:nil options:nil]; [self seg_viewDidAppear:animated]; } From 40992666afc1b9de0c16e914519b37740a8ed56b Mon Sep 17 00:00:00 2001 From: David Whetstone Date: Mon, 15 Jul 2019 22:04:52 -0700 Subject: [PATCH 05/11] Add tests for seg_topViewController --- Analytics.xcodeproj/project.pbxproj | 6 + .../xcschemes/AnalyticsTests.xcscheme | 13 ++ Analytics/Analytics.h | 1 + .../Internal/UIViewController+SEGScreen.m | 2 +- .../AnalyticsTests-Bridging-Header.h | 3 + AnalyticsTests/AutoScreenReportingTest.swift | 136 ++++++++++++++++++ .../UIViewController+SegScreenTest.h | 21 +++ AnalyticsTests/Utils/TestUtils.swift | 2 +- 8 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 AnalyticsTests/AutoScreenReportingTest.swift create mode 100644 AnalyticsTests/UIViewController+SegScreenTest.h diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj index 45add5ff1..d008a9e46 100644 --- a/Analytics.xcodeproj/project.pbxproj +++ b/Analytics.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3481C14AEC76E5A8DA64DA59 /* Pods_AnalyticsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89033CBF22319674E6CE7E61 /* Pods_AnalyticsTests.framework */; }; + 5AF0E8AE77F57B356DACCFE7 /* AutoScreenReportingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */; }; 630FC8EA2107F2A500A759C5 /* SEGScreenReporting.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E265C791FB1178C0030E08E /* IntegrationsManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */; }; 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEC1C702017EA370089C478 /* EndToEndTests.swift */; }; @@ -90,7 +91,9 @@ /* Begin PBXFileReference section */ 1DF03A6929EEFE7158913224 /* Pods-AnalyticsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AnalyticsTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests.debug.xcconfig"; sourceTree = ""; }; + 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoScreenReportingTest.swift; sourceTree = ""; }; 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SEGScreenReporting.h; sourceTree = ""; }; + 63E090D722DD49C300DEC7EC /* UIViewController+SegScreenTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SegScreenTest.h"; sourceTree = ""; }; 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsManagerTest.swift; sourceTree = ""; }; 6EEC1C702017EA370089C478 /* EndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndToEndTests.swift; sourceTree = ""; }; 89033CBF22319674E6CE7E61 /* Pods_AnalyticsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AnalyticsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -268,6 +271,8 @@ EADEB8EC1DECD335005322DA /* AnalyticsTests-Bridging-Header.h */, 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */, 6EEC1C702017EA370089C478 /* EndToEndTests.swift */, + 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */, + 63E090D722DD49C300DEC7EC /* UIViewController+SegScreenTest.h */, ); indentWidth = 2; path = AnalyticsTests; @@ -618,6 +623,7 @@ EADEB8EF1DECD335005322DA /* NSData+SEGGUNZIPP.m in Sources */, 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */, EADEB8F01DECD335005322DA /* TestUtils.swift in Sources */, + 5AF0E8AE77F57B356DACCFE7 /* AutoScreenReportingTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Analytics.xcodeproj/xcshareddata/xcschemes/AnalyticsTests.xcscheme b/Analytics.xcodeproj/xcshareddata/xcschemes/AnalyticsTests.xcscheme index 930361f39..513d0587e 100644 --- a/Analytics.xcodeproj/xcshareddata/xcschemes/AnalyticsTests.xcscheme +++ b/Analytics.xcodeproj/xcshareddata/xcschemes/AnalyticsTests.xcscheme @@ -5,6 +5,19 @@ + + + + + + *)top; [screenReporting seg_trackScreen:top name:name]; return; diff --git a/AnalyticsTests/AnalyticsTests-Bridging-Header.h b/AnalyticsTests/AnalyticsTests-Bridging-Header.h index 877104033..07afba377 100644 --- a/AnalyticsTests/AnalyticsTests-Bridging-Header.h +++ b/AnalyticsTests/AnalyticsTests-Bridging-Header.h @@ -12,7 +12,10 @@ #import #import #import +#import #import "NSData+SEGGUNZIPP.h" // Temp hack. We should fix the LSNocilla podspec to make this header publicly available #import "LSMatcher.h" + +#import "UIViewController+SegScreenTest.h" diff --git a/AnalyticsTests/AutoScreenReportingTest.swift b/AnalyticsTests/AutoScreenReportingTest.swift new file mode 100644 index 000000000..2bb715f87 --- /dev/null +++ b/AnalyticsTests/AutoScreenReportingTest.swift @@ -0,0 +1,136 @@ +// +// Created by David Whetstone on 2018-11-04. +// Copyright (c) 2018 Segment. All rights reserved. +// + +import Foundation +import Quick +import Nimble +import SwiftTryCatch +@testable import Analytics + +class AutoScreenReportingTests: QuickSpec { + + override func spec() { + + var window: UIWindow! + var rootVC: UIViewController! + + beforeEach { + let config = SEGAnalyticsConfiguration(writeKey: "foobar") + config.trackApplicationLifecycleEvents = true + config.recordScreenViews = true + + window = UIWindow() + rootVC = UIViewController() + window.addSubview(rootVC.view) + } + + + describe("given a single view controller") { + + it("seg_topViewController returns that view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === rootVC + } + } + + describe("given a presented view controller") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + rootVC.present(expectedVC, animated: false) + } + + it("seg_topViewController returns the presented view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a pushed view controller") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let nc = UINavigationController() + rootVC.present(nc, animated: false) + nc.pushViewController(expectedVC, animated: false) + } + + it("seg_topViewController returns the pushed view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a UITabBarController") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let tabBarController = UITabBarController() + rootVC.present(tabBarController, animated: false) + tabBarController.viewControllers = [UIViewController(), expectedVC, UIViewController()] + tabBarController.selectedIndex = 1 + } + + it("seg_topViewController returns the currently selected view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a custom container view controller conforming to SEGScreenReporting") { + + class CustomContainerViewController: UIViewController, SEGScreenReporting { + var selectedIndex: Int = 0 + var seg_mainViewController: UIViewController? { + return childViewControllers[selectedIndex] + } + } + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let containerVC = CustomContainerViewController() + rootVC.present(containerVC, animated: false) + [UIViewController(), expectedVC, UIViewController()].forEach { child in + containerVC.addChildViewController(child) + } + containerVC.selectedIndex = 1 + } + + it("seg_topViewController returns the currently selected view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a container view controller NOT conforming to SEGScreenReporting") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let containerVC = UIViewController() + rootVC.present(containerVC, animated: false) + [expectedVC, UIViewController(), UIViewController()].forEach { child in + containerVC.addChildViewController(child) + } + } + + it("seg_topViewController returns the first child view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + } +} + + diff --git a/AnalyticsTests/UIViewController+SegScreenTest.h b/AnalyticsTests/UIViewController+SegScreenTest.h new file mode 100644 index 000000000..9e63ade3c --- /dev/null +++ b/AnalyticsTests/UIViewController+SegScreenTest.h @@ -0,0 +1,21 @@ +// +// UIViewController+SegScreenTest.h +// Analytics +// +// Created by David Whetstone on 7/15/19. +// Copyright © 2019 Segment. All rights reserved. +// + +#ifndef UIViewController_SegScreenTest_h +#define UIViewController_SegScreenTest_h + + +@interface UIViewController (Testing) +/// We need to expose this normally private method to tests, as the public facing +/// `+ (UIViewController *)seg_topViewController` relies on the `application` property +/// of `SEGAnalyticsConfiguration`, which won't be set in these tests. ++ (UIViewController *)seg_topViewController:(UIViewController *)rootViewController; +@end + + +#endif /* UIViewController_SegScreenTest_h */ diff --git a/AnalyticsTests/Utils/TestUtils.swift b/AnalyticsTests/Utils/TestUtils.swift index dc4e85823..0440ddc28 100644 --- a/AnalyticsTests/Utils/TestUtils.swift +++ b/AnalyticsTests/Utils/TestUtils.swift @@ -82,7 +82,7 @@ class JsonGzippedBody : LSMatcher, LSMatcheable { } func matchesJson(_ json: AnyObject) -> Bool { - let actualValue : () -> NSObject! = { + let actualValue : () -> NSObject = { return json as! NSObject } let failureMessage = FailureMessage() From a6f08b263c82d82ead7322fc7f28894547bcdd70 Mon Sep 17 00:00:00 2001 From: Sergei Guselnikov Date: Thu, 23 Apr 2020 21:10:13 +0300 Subject: [PATCH 06/11] fixed a crash in file storage when trying to get a string stored using old SDK version (#880) --- .gitignore | 1 + Analytics/Classes/Internal/SEGFileStorage.m | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 53505db84..e7f55eeae 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ Carthage # itself has no pod dependencies, only our tests do. Pods/ .clang-format +.idea diff --git a/Analytics/Classes/Internal/SEGFileStorage.m b/Analytics/Classes/Internal/SEGFileStorage.m index c25594999..ed534dbab 100644 --- a/Analytics/Classes/Internal/SEGFileStorage.m +++ b/Analytics/Classes/Internal/SEGFileStorage.m @@ -109,10 +109,18 @@ - (void)setArray:(nullable NSArray *)array forKey:(NSString *)key - (nullable NSString *)stringForKey:(NSString *)key { - NSDictionary *data = [self jsonForKey:key]; - if (data) { + id data = [self jsonForKey:key]; + + if (data == nil) { + return nil; + } + + if ([data isKindOfClass:[NSString class]]) { + return data; + } else if ([data isKindOfClass:[NSDictionary class]]) { return data[key]; } + return nil; } From cb623dc4971c4cd0be8384a018b5e8c244b28471 Mon Sep 17 00:00:00 2001 From: Brandon Sneed Date: Fri, 1 May 2020 10:47:02 -0700 Subject: [PATCH 07/11] Fixed issue where build/version were removed from Application Opened events from background state. --- Analytics/Classes/SEGAnalytics.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Analytics/Classes/SEGAnalytics.m b/Analytics/Classes/SEGAnalytics.m index d5cc92134..9159fe7bb 100644 --- a/Analytics/Classes/SEGAnalytics.m +++ b/Analytics/Classes/SEGAnalytics.m @@ -169,8 +169,12 @@ - (void)_applicationWillEnterForeground if (!self.configuration.trackApplicationLifecycleEvents) { return; } + NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; [self track:@"Application Opened" properties:@{ @"from_background" : @YES, + @"version" : currentVersion ?: @"", + @"build" : currentBuild ?: @"", }]; } From e848f2a8d436fc26375978f239bb48e071c400e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Biesiada?= Date: Fri, 1 May 2020 20:23:04 +0200 Subject: [PATCH 08/11] Publish filenames used for data storage (#865) --- .../Classes/Integrations/SEGIntegrationsManager.h | 6 ++++++ .../Classes/Integrations/SEGIntegrationsManager.m | 14 +++++++------- Analytics/Classes/Internal/SEGSegmentIntegration.h | 7 +++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Analytics/Classes/Integrations/SEGIntegrationsManager.h b/Analytics/Classes/Integrations/SEGIntegrationsManager.h index bbd0e24ba..ec17275cb 100644 --- a/Analytics/Classes/Integrations/SEGIntegrationsManager.h +++ b/Analytics/Classes/Integrations/SEGIntegrationsManager.h @@ -9,6 +9,12 @@ #import #import "SEGMiddleware.h" +/** + * Filenames of "Application Support" files where essential data is stored. + */ +extern NSString *_Nonnull const kSEGAnonymousIdFilename; +extern NSString *_Nonnull const kSEGCachedSettingsFilename; + /** * NSNotification name, that is posted after integrations are loaded. */ diff --git a/Analytics/Classes/Integrations/SEGIntegrationsManager.m b/Analytics/Classes/Integrations/SEGIntegrationsManager.m index 7fdd46340..07399aa58 100644 --- a/Analytics/Classes/Integrations/SEGIntegrationsManager.m +++ b/Analytics/Classes/Integrations/SEGIntegrationsManager.m @@ -26,9 +26,9 @@ #import "SEGAliasPayload.h" NSString *SEGAnalyticsIntegrationDidStart = @"io.segment.analytics.integration.did.start"; -static NSString *const SEGAnonymousIdKey = @"SEGAnonymousId"; -static NSString *const kSEGAnonymousIdFilename = @"segment.anonymousId"; -static NSString *const SEGCachedSettingsKey = @"analytics.settings.v2.plist"; +NSString *const SEGAnonymousIdKey = @"SEGAnonymousId"; +NSString *const kSEGAnonymousIdFilename = @"segment.anonymousId"; +NSString *const kSEGCachedSettingsFilename = @"analytics.settings.v2.plist"; @interface SEGIdentifyPayload (AnonymousId) @@ -357,9 +357,9 @@ - (NSDictionary *)cachedSettings { if (!_cachedSettings) { #if TARGET_OS_TV - _cachedSettings = [self.userDefaultsStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{}; + _cachedSettings = [self.userDefaultsStorage dictionaryForKey:kSEGCachedSettingsFilename] ?: @{}; #else - _cachedSettings = [self.fileStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{}; + _cachedSettings = [self.fileStorage dictionaryForKey:kSEGCachedSettingsFilename] ?: @{}; #endif } @@ -375,9 +375,9 @@ - (void)setCachedSettings:(NSDictionary *)settings } #if TARGET_OS_TV - [self.userDefaultsStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey]; + [self.userDefaultsStorage setDictionary:_cachedSettings forKey:kSEGCachedSettingsFilename]; #else - [self.fileStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey]; + [self.fileStorage setDictionary:_cachedSettings forKey:kSEGCachedSettingsFilename]; #endif [self updateIntegrationsWithSettings:settings[@"integrations"]]; diff --git a/Analytics/Classes/Internal/SEGSegmentIntegration.h b/Analytics/Classes/Internal/SEGSegmentIntegration.h index 63942277c..c32e01d70 100644 --- a/Analytics/Classes/Internal/SEGSegmentIntegration.h +++ b/Analytics/Classes/Internal/SEGSegmentIntegration.h @@ -9,6 +9,13 @@ extern NSString *const SEGSegmentDidSendRequestNotification; extern NSString *const SEGSegmentRequestDidSucceedNotification; extern NSString *const SEGSegmentRequestDidFailNotification; +/** + * Filenames of "Application Support" files where essential data is stored. + */ +extern NSString *const kSEGUserIdFilename; +extern NSString *const kSEGQueueFilename; +extern NSString *const kSEGTraitsFilename; + @interface SEGSegmentIntegration : NSObject From be66eda3c2c9857e31ff4bf510b7e57984cb4935 Mon Sep 17 00:00:00 2001 From: Brandon Sneed Date: Wed, 6 May 2020 13:16:33 -0700 Subject: [PATCH 09/11] Remove tvos test from scheme setup. --- .../xcshareddata/xcschemes/Analytics.xcscheme | 14 -------------- Podfile.lock | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme b/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme index 8c0862b7c..cce21eeec 100644 --- a/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme +++ b/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme @@ -20,20 +20,6 @@ ReferencedContainer = "container:Analytics.xcodeproj"> - - - - Date: Wed, 6 May 2020 13:35:57 -0700 Subject: [PATCH 10/11] Fixed selector reference. --- Analytics/Classes/SEGAnalyticsConfiguration.m | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Analytics/Classes/SEGAnalyticsConfiguration.m b/Analytics/Classes/SEGAnalyticsConfiguration.m index 46d712351..b8b79806c 100644 --- a/Analytics/Classes/SEGAnalyticsConfiguration.m +++ b/Analytics/Classes/SEGAnalyticsConfiguration.m @@ -68,10 +68,7 @@ - (instancetype)init _factories = [NSMutableArray array]; Class applicationClass = NSClassFromString(@"UIApplication"); if (applicationClass) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - _application = [applicationClass performSelector:NSSelectorFromString(@"sharedApplication")]; -#pragma clang diagnostic pop + _application = [applicationClass performSelector:@selector(sharedApplication)]; } } return self; From 9d43b3a9f84f2c277482d9074fa7f87485574fca Mon Sep 17 00:00:00 2001 From: Brandon Sneed Date: Wed, 6 May 2020 13:46:04 -0700 Subject: [PATCH 11/11] Removed unused code. --- Analytics/Classes/Internal/SEGAnalyticsUtils.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/Analytics/Classes/Internal/SEGAnalyticsUtils.h b/Analytics/Classes/Internal/SEGAnalyticsUtils.h index 82b8aa8e2..6214a9a3a 100644 --- a/Analytics/Classes/Internal/SEGAnalyticsUtils.h +++ b/Analytics/Classes/Internal/SEGAnalyticsUtils.h @@ -5,9 +5,6 @@ NS_ASSUME_NONNULL_BEGIN NSString *GenerateUUIDString(void); -// Validation Utils -BOOL serializableDictionaryTypes(NSDictionary *dict); - // Date Utils NSString *iso8601FormattedString(NSDate *date); NSString *iso8601NanoFormattedString(NSDate *date);