Skip to content

Commit 07515b3

Browse files
[tvOS] Move SEGQueue from UserDefaults to caches directory (#861)
* Stops using UserDefaults for queue on tvOS and uses NSCachesDirectory Changes storage to fileStorage and userDefaultsStorage. Utilizes userDefaults on tvOS for information such as anonymousID and configuration, but moves tvOS's queue into the NSCachesDirectory. The reasoning is that tvOS has a 1mb limit for UserDefaults and the queue can grow rapidly in size, leading to app crashes when saving more than 1mb of data to UserDefaults. * Adds a constant for key. Seperate cache dir and appSupport dir functions. Removes unused init. * Adds functionality to remove old UserDefaults queue on tvOS. Updates migrated removal block to account for tvOS now that the queue is no longer in UserDefaults. Adds back in a #else and #endif that was accidently removed. * Adds tvOS unit test target * Adds new AnalyticsTestsTVOS scheme * Updates pods to include all test pods for AnalyticsTestsTVOS * Fixes unit test import for QuickTVOS * Add tvOS options for make file * Enabled code coverage on tvOS tests * Fix up unit test warning "result of expect is never nil" * Adds test to ensure that UserDefaults SEGQueue is cleared on initialization for tvOS & iOS * Adds test to ensure SEGQueue is empty when missing form file storage * Reverts unnecessary import for QuickTVOS * Adds test for FileStorage caches directory helper * Fix up: Adds SwiftTryCatch pod to tvOS test target * Fix up makefile to have correct build target for build-ios & build-tvos * Fix up: updates xcodebuild destination to match devices found on circleci * Break up ios and tvos build/test steps * Circleci: Cache pods * Fix up: tvOS test build * Fix up spacing * Fix up flaky unit test Co-authored-by: Connor Ricks <[email protected]>
1 parent 89ae097 commit 07515b3

18 files changed

+448
-126
lines changed

.circleci/config.yml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@ jobs:
1313
sudo gem install xcpretty
1414
sudo gem install cocoapods -v $(var=$(tail -1 Podfile.lock); echo ${var##COCOAPODS:})
1515
16+
- restore_cache:
17+
key: 1-pods-{{ checksum "Podfile.lock" }}
1618
- run:
17-
name: Fetch Cocoapods specs
18-
command: curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
19+
name: Install CocoaPods
20+
command: |
21+
if [ ! -d "Pods" ]
22+
then
23+
make dependencies
24+
fi
25+
- save_cache:
26+
key: 1-pods-{{ checksum "Podfile.lock" }}
27+
paths:
28+
- ./Pods
1929

20-
- run: make dependencies
21-
- run: make build-pretty
22-
- run: make test-pretty
30+
- run: make build-ios
31+
- run: make test-ios
32+
- run: make build-tvos
33+
- run: make test-tvos
2334
- run: make lint
2435
- run: make carthage
2536

Analytics.xcodeproj/project.pbxproj

Lines changed: 218 additions & 28 deletions
Large diffs are not rendered by default.

Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@
4848
ReferencedContainer = "container:Analytics.xcodeproj">
4949
</BuildableReference>
5050
</TestableReference>
51+
<TestableReference
52+
skipped = "NO">
53+
<BuildableReference
54+
BuildableIdentifier = "primary"
55+
BlueprintIdentifier = "9D8CE58B23EE014E00197D0C"
56+
BuildableName = "AnalyticsTestsTVOS.xctest"
57+
BlueprintName = "AnalyticsTestsTVOS"
58+
ReferencedContainer = "container:Analytics.xcodeproj">
59+
</BuildableReference>
60+
</TestableReference>
5161
</Testables>
5262
</TestAction>
5363
<LaunchAction
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1130"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
</BuildAction>
9+
<TestAction
10+
buildConfiguration = "Debug"
11+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
12+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
shouldUseLaunchSchemeArgsEnv = "YES"
14+
codeCoverageEnabled = "YES">
15+
<Testables>
16+
<TestableReference
17+
skipped = "NO">
18+
<BuildableReference
19+
BuildableIdentifier = "primary"
20+
BlueprintIdentifier = "9D8CE58B23EE014E00197D0C"
21+
BuildableName = "AnalyticsTestsTVOS.xctest"
22+
BlueprintName = "AnalyticsTestsTVOS"
23+
ReferencedContainer = "container:Analytics.xcodeproj">
24+
</BuildableReference>
25+
</TestableReference>
26+
</Testables>
27+
</TestAction>
28+
<LaunchAction
29+
buildConfiguration = "Debug"
30+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
31+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
32+
launchStyle = "0"
33+
useCustomWorkingDirectory = "NO"
34+
ignoresPersistentStateOnLaunch = "NO"
35+
debugDocumentVersioning = "YES"
36+
debugServiceExtension = "internal"
37+
allowLocationSimulation = "YES">
38+
</LaunchAction>
39+
<ProfileAction
40+
buildConfiguration = "Release"
41+
shouldUseLaunchSchemeArgsEnv = "YES"
42+
savedToolIdentifier = ""
43+
useCustomWorkingDirectory = "NO"
44+
debugDocumentVersioning = "YES">
45+
</ProfileAction>
46+
<AnalyzeAction
47+
buildConfiguration = "Debug">
48+
</AnalyzeAction>
49+
<ArchiveAction
50+
buildConfiguration = "Release"
51+
revealArchiveInOrganizer = "YES">
52+
</ArchiveAction>
53+
</Scheme>

Analytics/Classes/Integrations/SEGIntegrationsManager.m

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
NSString *SEGAnalyticsIntegrationDidStart = @"io.segment.analytics.integration.did.start";
2929
static NSString *const SEGAnonymousIdKey = @"SEGAnonymousId";
3030
static NSString *const kSEGAnonymousIdFilename = @"segment.anonymousId";
31+
static NSString *const SEGCachedSettingsKey = @"analytics.settings.v2.plist";
3132

3233

3334
@interface SEGAnalyticsConfiguration (Private)
@@ -51,7 +52,8 @@ @interface SEGIntegrationsManager ()
5152
@property (nonatomic, copy) NSString *cachedAnonymousId;
5253
@property (nonatomic, strong) SEGHTTPClient *httpClient;
5354
@property (nonatomic, strong) NSURLSessionDataTask *settingsRequest;
54-
@property (nonatomic, strong) id<SEGStorage> storage;
55+
@property (nonatomic, strong) id<SEGStorage> userDefaultsStorage;
56+
@property (nonatomic, strong) id<SEGStorage> fileStorage;
5557

5658
@end
5759

@@ -71,14 +73,17 @@ - (instancetype _Nonnull)initWithAnalytics:(SEGAnalytics *_Nonnull)analytics
7173
self.serialQueue = seg_dispatch_queue_create_specific("io.segment.analytics", DISPATCH_QUEUE_SERIAL);
7274
self.messageQueue = [[NSMutableArray alloc] init];
7375
self.httpClient = [[SEGHTTPClient alloc] initWithRequestFactory:configuration.requestFactory];
74-
#if TARGET_OS_TV
75-
self.storage = [[SEGUserDefaultsStorage alloc] initWithDefaults:[NSUserDefaults standardUserDefaults] namespacePrefix:nil crypto:configuration.crypto];
76-
#else
77-
self.storage = [[SEGFileStorage alloc] initWithFolder:[SEGFileStorage applicationSupportDirectoryURL] crypto:configuration.crypto];
78-
#endif
76+
77+
self.userDefaultsStorage = [[SEGUserDefaultsStorage alloc] initWithDefaults:[NSUserDefaults standardUserDefaults] namespacePrefix:nil crypto:configuration.crypto];
78+
#if TARGET_OS_TV
79+
self.fileStorage = [[SEGFileStorage alloc] initWithFolder:[SEGFileStorage cachesDirectoryURL] crypto:configuration.crypto];
80+
#else
81+
self.fileStorage = [[SEGFileStorage alloc] initWithFolder:[SEGFileStorage applicationSupportDirectoryURL] crypto:configuration.crypto];
82+
#endif
83+
7984
self.cachedAnonymousId = [self loadOrGenerateAnonymousID:NO];
8085
NSMutableArray *factories = [[configuration factories] mutableCopy];
81-
[factories addObject:[[SEGSegmentIntegrationFactory alloc] initWithHTTPClient:self.httpClient storage:self.storage]];
86+
[factories addObject:[[SEGSegmentIntegrationFactory alloc] initWithHTTPClient:self.httpClient fileStorage:self.fileStorage userDefaultsStorage:self.userDefaultsStorage]];
8287
self.factories = [factories copy];
8388
self.integrations = [NSMutableDictionary dictionaryWithCapacity:factories.count];
8489
self.registeredIntegrations = [NSMutableDictionary dictionaryWithCapacity:factories.count];
@@ -285,9 +290,9 @@ - (NSString *)getAnonymousId;
285290
- (NSString *)loadOrGenerateAnonymousID:(BOOL)reset
286291
{
287292
#if TARGET_OS_TV
288-
NSString *anonymousId = [self.storage stringForKey:SEGAnonymousIdKey];
293+
NSString *anonymousId = [self.userDefaultsStorage stringForKey:SEGAnonymousIdKey];
289294
#else
290-
NSString *anonymousId = [self.storage stringForKey:kSEGAnonymousIdFilename];
295+
NSString *anonymousId = [self.fileStorage stringForKey:kSEGAnonymousIdFilename];
291296
#endif
292297

293298
if (!anonymousId || reset) {
@@ -297,9 +302,9 @@ - (NSString *)loadOrGenerateAnonymousID:(BOOL)reset
297302
anonymousId = GenerateUUIDString();
298303
SEGLog(@"New anonymousId: %@", anonymousId);
299304
#if TARGET_OS_TV
300-
[self.storage setString:anonymousId forKey:SEGAnonymousIdKey];
305+
[self.userDefaultsStorage setString:anonymousId forKey:SEGAnonymousIdKey];
301306
#else
302-
[self.storage setString:anonymousId forKey:kSEGAnonymousIdFilename];
307+
[self.fileStorage setString:anonymousId forKey:kSEGAnonymousIdFilename];
303308
#endif
304309
}
305310
return anonymousId;
@@ -309,9 +314,9 @@ - (void)saveAnonymousId:(NSString *)anonymousId
309314
{
310315
self.cachedAnonymousId = anonymousId;
311316
#if TARGET_OS_TV
312-
[self.storage setString:anonymousId forKey:SEGAnonymousIdKey];
317+
[self.userDefaultsStorage setString:anonymousId forKey:SEGAnonymousIdKey];
313318
#else
314-
[self.storage setString:anonymousId forKey:@"segment.anonymousId"];
319+
[self.fileStorage setString:anonymousId forKey:kSEGAnonymousIdFilename];
315320
#endif
316321
}
317322

@@ -324,8 +329,14 @@ - (void)flush
324329

325330
- (NSDictionary *)cachedSettings
326331
{
327-
if (!_cachedSettings)
328-
_cachedSettings = [self.storage dictionaryForKey:@"analytics.settings.v2.plist"] ?: @{};
332+
if (!_cachedSettings) {
333+
#if TARGET_OS_TV
334+
_cachedSettings = [self.userDefaultsStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{};
335+
#else
336+
_cachedSettings = [self.fileStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{};
337+
#endif
338+
}
339+
329340
return _cachedSettings;
330341
}
331342

@@ -336,7 +347,12 @@ - (void)setCachedSettings:(NSDictionary *)settings
336347
// [@{} writeToURL:settingsURL atomically:YES];
337348
return;
338349
}
339-
[self.storage setDictionary:_cachedSettings forKey:@"analytics.settings.v2.plist"];
350+
351+
#if TARGET_OS_TV
352+
[self.userDefaultsStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey];
353+
#else
354+
[self.fileStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey];
355+
#endif
340356

341357
[self updateIntegrationsWithSettings:settings[@"integrations"]];
342358
}

Analytics/Classes/Internal/SEGFileStorage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
@property (nonatomic, strong, nullable) id<SEGCrypto> crypto;
1515

16-
- (instancetype _Nonnull)init;
1716
- (instancetype _Nonnull)initWithFolder:(NSURL *_Nonnull)folderURL crypto:(id<SEGCrypto> _Nullable)crypto;
1817

1918
- (NSURL *_Nonnull)urlForKey:(NSString *_Nonnull)key;
2019

2120
+ (NSURL *_Nullable)applicationSupportDirectoryURL;
21+
+ (NSURL *_Nullable)cachesDirectoryURL;
2222

2323
@end

Analytics/Classes/Internal/SEGFileStorage.m

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ @interface SEGFileStorage ()
1919

2020
@implementation SEGFileStorage
2121

22-
- (instancetype)init
23-
{
24-
return [self initWithFolder:[SEGFileStorage applicationSupportDirectoryURL] crypto:nil];
25-
}
26-
2722
- (instancetype)initWithFolder:(NSURL *)folderURL crypto:(id<SEGCrypto>)crypto
2823
{
2924
if (self = [super init]) {
@@ -121,11 +116,19 @@ - (void)setString:(NSString *)string forKey:(NSString *)key
121116
[self setJSON:data forKey:key];
122117
}
123118

119+
124120
+ (NSURL *)applicationSupportDirectoryURL
125121
{
126122
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
127-
NSString *supportPath = [paths firstObject];
128-
return [NSURL fileURLWithPath:supportPath];
123+
NSString *storagePath = [paths firstObject];
124+
return [NSURL fileURLWithPath:storagePath];
125+
}
126+
127+
+ (NSURL *)cachesDirectoryURL
128+
{
129+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
130+
NSString *storagePath = [paths firstObject];
131+
return [NSURL fileURLWithPath:storagePath];
129132
}
130133

131134
- (NSURL *)urlForKey:(NSString *)key

Analytics/Classes/Internal/SEGSegmentIntegration.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extern NSString *const SEGSegmentRequestDidFailNotification;
1212

1313
@interface SEGSegmentIntegration : NSObject <SEGIntegration>
1414

15-
- (id)initWithAnalytics:(SEGAnalytics *)analytics httpClient:(SEGHTTPClient *)httpClient storage:(id<SEGStorage>)storage;
15+
- (id)initWithAnalytics:(SEGAnalytics *)analytics httpClient:(SEGHTTPClient *)httpClient fileStorage:(id<SEGStorage>)fileStorage userDefaultsStorage:(id<SEGStorage>)userDefaultsStorage;
1616

1717
@end
1818

0 commit comments

Comments
 (0)