From 88c7a3cd6d62e73b6d7faab450a0c7e43137acde Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Wed, 10 Aug 2016 16:30:40 -0700 Subject: [PATCH 1/7] remove initializerQueue, move db logic from init to initializeApiKey, guarded db interactions with apikey check --- Amplitude/Amplitude.m | 128 +++++++++++++++++++------------- AmplitudeTests/AmplitudeTests.m | 6 +- AmplitudeTests/BaseTestCase.m | 2 +- AmplitudeTests/SessionTests.m | 7 +- CHANGELOG.md | 3 + 5 files changed, 87 insertions(+), 59 deletions(-) diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index dba5eb97..889530b7 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -49,7 +49,6 @@ @interface Amplitude () @property (nonatomic, strong) NSOperationQueue *backgroundQueue; -@property (nonatomic, strong) NSOperationQueue *initializerQueue; @property (nonatomic, strong) AMPDatabaseHelper *dbHelper; @property (nonatomic, assign) BOOL initialized; @property (nonatomic, assign) BOOL sslPinningEnabled; @@ -226,7 +225,6 @@ - (id)initWithInstanceName:(NSString*) instanceName _backoffUpload = NO; _offline = NO; _instanceName = SAFE_ARC_RETAIN(instanceName); - _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:instanceName]); self.eventUploadThreshold = kAMPEventUploadThreshold; self.eventMaxCount = kAMPEventMaxCount; @@ -235,21 +233,18 @@ - (id)initWithInstanceName:(NSString*) instanceName self.minTimeBetweenSessionsMillis = kAMPMinTimeBetweenSessionsMillis; _backoffUploadBatchSize = self.eventUploadMaxBatchSize; - _initializerQueue = [[NSOperationQueue alloc] init]; _backgroundQueue = [[NSOperationQueue alloc] init]; // Force method calls to happen in FIFO order by only allowing 1 concurrent operation [_backgroundQueue setMaxConcurrentOperationCount:1]; - // Ensure initialize finishes running asynchronously before other calls are run - [_backgroundQueue setSuspended:YES]; // Name the queue so runOnBackgroundQueue can tell which queue an operation is running _backgroundQueue.name = BACKGROUND_QUEUE_NAME; - [_initializerQueue addOperationWithBlock:^{ + [_backgroundQueue addOperationWithBlock:^{ _deviceInfo = [[AMPDeviceInfo alloc] init]; - _uploadTaskID = UIBackgroundTaskInvalid; - + + // resolve path strings NSString *eventsDataDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *propertyListPath = [eventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.plist"]; if (![_instanceName isEqualToString:kAMPDefaultInstance]) { @@ -257,9 +252,9 @@ - (id)initWithInstanceName:(NSString*) instanceName } _propertyListPath = SAFE_ARC_RETAIN(propertyListPath); _eventsDataPath = SAFE_ARC_RETAIN([eventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.archiveDict"]); - [self upgradePrefs]; + [self upgradePrefs]; // migrate legacy prefs file - // Load propertyList object + // Load propertyList object, which contains current db version _propertyList = SAFE_ARC_RETAIN([self deserializePList:_propertyListPath]); if (!_propertyList) { _propertyList = SAFE_ARC_RETAIN([NSMutableDictionary dictionary]); @@ -271,42 +266,6 @@ - (id)initWithInstanceName:(NSString*) instanceName } else { AMPLITUDE_LOG(@"Loaded from %@", _propertyListPath); } - - // update database if necessary - int oldDBVersion = 1; - NSNumber *oldDBVersionSaved = [_propertyList objectForKey:DATABASE_VERSION]; - if (oldDBVersionSaved != nil) { - oldDBVersion = [oldDBVersionSaved intValue]; - } - - // update the database - if (oldDBVersion < kAMPDBVersion) { - if ([self.dbHelper upgrade:oldDBVersion newVersion:kAMPDBVersion]) { - [_propertyList setObject:[NSNumber numberWithInt:kAMPDBVersion] forKey:DATABASE_VERSION]; - [self savePropertyList]; - } - } - - // only on default instance, migrate all of old _eventsData object to database store if database just created - if ([_instanceName isEqualToString:kAMPDefaultInstance] && oldDBVersion < kAMPDBFirstVersion) { - if ([self migrateEventsDataToDB]) { - // delete events data so don't need to migrate next time - if ([[NSFileManager defaultManager] fileExistsAtPath:_eventsDataPath]) { - [[NSFileManager defaultManager] removeItemAtPath:_eventsDataPath error:NULL]; - } - } - } - SAFE_ARC_RELEASE(_eventsDataPath); - - // try to restore previous session - long long previousSessionId = [self previousSessionId]; - if (previousSessionId >= 0) { - _sessionId = previousSessionId; - } - - [self initializeDeviceId]; - - [_backgroundQueue setSuspended:NO]; }]; // CLLocationManager must be created on the main thread @@ -317,8 +276,6 @@ - (id)initWithInstanceName:(NSString*) instanceName SEL setDelegate = NSSelectorFromString(@"setDelegate:"); [_locationManager performSelector:setDelegate withObject:_locationManagerDelegate]; }); - - [self addObservers]; } return self; } @@ -411,12 +368,12 @@ - (void) dealloc { // Release instance variables SAFE_ARC_RELEASE(_deviceInfo); - SAFE_ARC_RELEASE(_initializerQueue); SAFE_ARC_RELEASE(_lastKnownLocation); SAFE_ARC_RELEASE(_locationManager); SAFE_ARC_RELEASE(_locationManagerDelegate); SAFE_ARC_RELEASE(_propertyList); SAFE_ARC_RELEASE(_propertyListPath); + SAFE_ARC_RELEASE(_eventsDataPath); SAFE_ARC_RELEASE(_dbHelper); SAFE_ARC_RELEASE(_instanceName); @@ -464,8 +421,41 @@ - (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId: SAFE_ARC_RETAIN(apiKey); SAFE_ARC_RELEASE(_apiKey); _apiKey = apiKey; + _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:_instanceName]); [self runOnBackgroundQueue:^{ + // update database if necessary + int oldDBVersion = 1; + NSNumber *oldDBVersionSaved = [_propertyList objectForKey:DATABASE_VERSION]; + if (oldDBVersionSaved != nil) { + oldDBVersion = [oldDBVersionSaved intValue]; + } + + // update the database + if (oldDBVersion < kAMPDBVersion) { + if ([self.dbHelper upgrade:oldDBVersion newVersion:kAMPDBVersion]) { + [_propertyList setObject:[NSNumber numberWithInt:kAMPDBVersion] forKey:DATABASE_VERSION]; + [self savePropertyList]; + } + } + + // only on default instance, migrate all of old _eventsData object to database store if database just created + if ([_instanceName isEqualToString:kAMPDefaultInstance] && oldDBVersion < kAMPDBFirstVersion) { + if ([self migrateEventsDataToDB]) { + // delete events data so don't need to migrate next time + if ([[NSFileManager defaultManager] fileExistsAtPath:_eventsDataPath]) { + [[NSFileManager defaultManager] removeItemAtPath:_eventsDataPath error:NULL]; + } + } + } + + // try to restore previous session + long long previousSessionId = [self previousSessionId]; + if (previousSessionId >= 0) { + _sessionId = previousSessionId; + } + [self initializeDeviceId]; + if (setUserId) { [self setUserId:userId]; } else { @@ -473,6 +463,9 @@ - (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId: } }]; + // now we can add observers after setting apikey since enterBackground saves timestamp to DB + [self addObservers]; + UIApplication *app = [self getSharedApplication]; if (app != nil) { UIApplicationState state = app.applicationState; @@ -1218,6 +1211,11 @@ - (void)startSession - (void)identify:(AMPIdentify *)identify { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling identify"); + return; + } + if (identify == nil || [identify.userPropertyOperations count] == 0) { return; } @@ -1228,6 +1226,11 @@ - (void)identify:(AMPIdentify *)identify - (void)setUserProperties:(NSDictionary*) userProperties { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setUserProperties"); + return; + } + if (userProperties == nil || ![self isArgument:userProperties validType:[NSDictionary class] methodName:@"setUserProperties:"] || [userProperties count] == 0) { return; } @@ -1252,6 +1255,11 @@ - (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace - (void)clearUserProperties { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling clearUserProperties"); + return; + } + AMPIdentify *identify = [[AMPIdentify identify] clearAll]; [self identify:identify]; } @@ -1276,6 +1284,11 @@ - (void)setGroup:(NSString *)groupType groupName:(NSObject *)groupName - (void)setUserId:(NSString*) userId { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setUserId"); + return; + } + if (!(userId == nil || [self isArgument:userId validType:[NSString class] methodName:@"setUserId:"])) { return; } @@ -1290,6 +1303,11 @@ - (void)setUserId:(NSString*) userId - (void)setOptOut:(BOOL)enabled { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setOptOut"); + return; + } + [self runOnBackgroundQueue:^{ NSNumber *value = [NSNumber numberWithBool:enabled]; (void) [self.dbHelper insertOrReplaceKeyLongValue:OPT_OUT value:value]; @@ -1300,7 +1318,7 @@ - (void)setOffline:(BOOL)offline { _offline = offline; - if (!_offline) { + if (!_offline && _apiKey != nil) { [self uploadEvents]; } } @@ -1313,12 +1331,22 @@ - (void)setEventUploadMaxBatchSize:(int) eventUploadMaxBatchSize - (BOOL)optOut { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before toggling and fetching optOut"); + return NO; + } return [[self.dbHelper getLongValue:OPT_OUT] boolValue]; } - (void)setDeviceId:(NSString*)deviceId { + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setDeviceId"); + return; + } + if (![self isValidDeviceId:deviceId]) { + AMPLITUDE_ERROR(@"ERROR: invalid deviceId '%@', skipping setDeviceId", deviceId); return; } diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 140aa26e..38129363 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -73,12 +73,12 @@ - (void)testInstanceWithName { - (void)testInitWithInstanceName { Amplitude *a = [Amplitude instanceWithName:@"APP1"]; - [a flushQueueWithQueue:a.initializerQueue]; + [a flushQueue]; XCTAssertEqualObjects(a.instanceName, @"app1"); XCTAssertTrue([a.propertyListPath rangeOfString:@"com.amplitude.plist_app1"].location != NSNotFound); Amplitude *b = [Amplitude instanceWithName:[kAMPDefaultInstance uppercaseString]]; - [b flushQueueWithQueue:b.initializerQueue]; + [b flushQueue]; XCTAssertEqualObjects(b.instanceName, kAMPDefaultInstance); XCTAssertTrue([b.propertyListPath rangeOfString:@"com.amplitude.plist"].location != NSNotFound); XCTAssertTrue([ b.propertyListPath rangeOfString:@"com.amplitude.plist_"].location == NSNotFound); @@ -110,6 +110,8 @@ - (void)testSeparateInstancesLogEventsSeparate { [newDBHelper2 resetDB:NO]; // setup existing database file, init default instance + [[Amplitude instance] initializeApiKey:apiKey]; + [[Amplitude instance] flushQueue]; [oldDbHelper insertOrReplaceKeyLongValue:@"sequence_number" value:[NSNumber numberWithLongLong:1000]]; [oldDbHelper addEvent:@"{\"event_type\":\"oldEvent\"}"]; [oldDbHelper addIdentify:@"{\"event_type\":\"$identify\"}"]; diff --git a/AmplitudeTests/BaseTestCase.m b/AmplitudeTests/BaseTestCase.m index 2d6ddd75..f147ad0e 100644 --- a/AmplitudeTests/BaseTestCase.m +++ b/AmplitudeTests/BaseTestCase.m @@ -30,12 +30,12 @@ - (void)setUp { XCTAssertTrue([self.databaseHelper resetDB:NO]); [self.amplitude init]; + [self.amplitude flushQueue]; self.amplitude.sslPinningEnabled = NO; } - (void)tearDown { // Ensure all background operations are done - [self.amplitude flushQueueWithQueue:self.amplitude.initializerQueue]; [self.amplitude flushQueue]; SAFE_ARC_RELEASE(_amplitude); SAFE_ARC_RELEASE(_databaseHelper); diff --git a/AmplitudeTests/SessionTests.m b/AmplitudeTests/SessionTests.m index 23ea5282..7abeb2e6 100644 --- a/AmplitudeTests/SessionTests.m +++ b/AmplitudeTests/SessionTests.m @@ -42,7 +42,6 @@ - (void)testSessionAutoStartedBackground { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude reject] enterForeground]; [mockAmplitude initializeApiKey:apiKey]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; [mockAmplitude flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); @@ -56,7 +55,6 @@ - (void)testSessionAutoStartedInactive { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude expect] enterForeground]; [mockAmplitude initializeApiKey:apiKey]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; [mockAmplitude flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); @@ -70,7 +68,6 @@ - (void)testSessionHandling { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); XCTAssertEqual([mockAmplitude sessionId], 1000000); @@ -140,7 +137,7 @@ - (void)testSessionHandling { - (void)testEnterBackgroundDoesNotTrackEvent { [self.amplitude initializeApiKey:apiKey userId:nil]; - [self.amplitude flushQueueWithQueue:self.amplitude.initializerQueue]; + [self.amplitude flushQueue]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil userInfo:nil]; @@ -156,7 +153,6 @@ - (void)testTrackSessionEvents { [mockAmplitude setTrackingSessionEvents:YES]; [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); @@ -192,7 +188,6 @@ - (void)testSessionEventsOn32BitDevices { [mockAmplitude setTrackingSessionEvents:YES]; [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e8b1d6a..82bf49f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +* Migrate all database logic from `init` to `initializeApiKey`. +* Run `init` logic on `backgroundQueue`, removing need for separate `initializerQueue`. + ### 3.8.3 (July 18, 2016) * Fix overflow bug for long long values saved to Sqlite DB on 32-bit devices. From 9e60cd5fef0466de074c42394ab068606df5c250 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Thu, 11 Aug 2016 14:48:25 -0700 Subject: [PATCH 2/7] update all getDatabaseHelper calls to include apiKey --- Amplitude/AMPDatabaseHelper.h | 3 +- Amplitude/AMPDatabaseHelper.m | 31 +++--- Amplitude/AMPUtils.h | 1 + Amplitude/AMPUtils.m | 17 +++ Amplitude/Amplitude.m | 25 +---- AmplitudeTests/AMPDatabaseHelperTests.m | 35 +++--- AmplitudeTests/Amplitude+Test.m | 10 +- AmplitudeTests/AmplitudeTests.m | 135 +++++++++++------------- AmplitudeTests/BaseTestCase.m | 2 +- AmplitudeTests/SessionTests.m | 8 +- AmplitudeTests/SetupTests.m | 23 ++-- 11 files changed, 137 insertions(+), 153 deletions(-) diff --git a/Amplitude/AMPDatabaseHelper.h b/Amplitude/AMPDatabaseHelper.h index d140b4eb..eab5378d 100644 --- a/Amplitude/AMPDatabaseHelper.h +++ b/Amplitude/AMPDatabaseHelper.h @@ -10,8 +10,7 @@ @property (nonatomic, strong, readonly) NSString *databasePath; -+ (AMPDatabaseHelper*)getDatabaseHelper; -+ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName; ++ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey; - (BOOL)createTables; - (BOOL)dropTables; - (BOOL)upgrade:(int) oldVersion newVersion:(int) newVersion; diff --git a/Amplitude/AMPDatabaseHelper.m b/Amplitude/AMPDatabaseHelper.m index 817adedb..bd668b3a 100644 --- a/Amplitude/AMPDatabaseHelper.m +++ b/Amplitude/AMPDatabaseHelper.m @@ -70,12 +70,7 @@ @implementation AMPDatabaseHelper static NSString *const GET_VALUE = @"SELECT %@, %@ FROM %@ WHERE %@ = ?;"; -+ (AMPDatabaseHelper*)getDatabaseHelper -{ - return [AMPDatabaseHelper getDatabaseHelper:nil]; -} - -+ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName ++ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey { static NSMutableDictionary *_instances = nil; static dispatch_once_t onceToken; @@ -92,7 +87,7 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName @synchronized(_instances) { dbHelper = [_instances objectForKey:instanceName]; if (dbHelper == nil) { - dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName]; + dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName andApiKey:apiKey]; [_instances setObject:dbHelper forKey:instanceName]; SAFE_ARC_RELEASE(dbHelper); } @@ -100,24 +95,24 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName return dbHelper; } -- (id)init -{ - return [self initWithInstanceName:nil]; -} - -- (id)initWithInstanceName:(NSString*) instanceName +// instanceName should not be null, getDatabaseHelper will guard +// apiKey should only be null for testing - Amplitude client will guard +- (id)initWithInstanceName:(NSString*) instanceName andApiKey:(NSString*) apiKey { - if ([AMPUtils isEmptyString:instanceName]) { - instanceName = kAMPDefaultInstance; - } - instanceName = [instanceName lowercaseString]; - if ((self = [super init])) { NSString *databaseDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *databasePath = [databaseDirectory stringByAppendingPathComponent:@"com.amplitude.database"]; if (![instanceName isEqualToString:kAMPDefaultInstance]) { databasePath = [NSString stringWithFormat:@"%@_%@", databasePath, instanceName]; } + + // migrate to new db filename +// if (![AMPUtils isEmptyString:apiKey]) { +// NSString *newDatabasePath = [NSString stringWithFormat:@"%@_%@", databasePath, apiKey]; +//// [AMPUtils moveFileIfNotExists:databasePath to:newDatabasePath]; +//// databasePath = newDatabasePath; +// } + _databasePath = SAFE_ARC_RETAIN(databasePath); _queue = dispatch_queue_create([QUEUE_NAME UTF8String], NULL); dispatch_queue_set_specific(_queue, kDispatchQueueKey, (__bridge void *)self, NULL); diff --git a/Amplitude/AMPUtils.h b/Amplitude/AMPUtils.h index a80d8ff9..7b8c7059 100644 --- a/Amplitude/AMPUtils.h +++ b/Amplitude/AMPUtils.h @@ -12,5 +12,6 @@ + (id) makeJSONSerializable:(id) obj; + (BOOL) isEmptyString:(NSString*) str; + (NSDictionary*) validateGroups:(NSDictionary*) obj; ++ (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to; @end diff --git a/Amplitude/AMPUtils.m b/Amplitude/AMPUtils.m index 45888039..c7886159 100644 --- a/Amplitude/AMPUtils.m +++ b/Amplitude/AMPUtils.m @@ -141,4 +141,21 @@ + (NSDictionary *) validateGroups:(NSDictionary *) obj return [NSDictionary dictionaryWithDictionary:dict]; } ++ (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error; + if (![fileManager fileExistsAtPath:to] && + [fileManager fileExistsAtPath:from]) { + if ([fileManager copyItemAtPath:from toPath:to error:&error]) { + AMPLITUDE_LOG(@"INFO: copied %@ to %@", from, to); + [fileManager removeItemAtPath:from error:NULL]; + } else { + AMPLITUDE_LOG(@"WARN: Copy from %@ to %@ failed: %@", from, to, error); + return NO; + } + } + return YES; +} + @end diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 889530b7..3c71a0cb 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -288,7 +288,7 @@ - (BOOL) migrateEventsDataToDB return NO; } - AMPDatabaseHelper *defaultDbHelper = [AMPDatabaseHelper getDatabaseHelper]; + AMPDatabaseHelper *defaultDbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:_apiKey]; BOOL success = YES; // migrate events @@ -421,7 +421,7 @@ - (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId: SAFE_ARC_RETAIN(apiKey); SAFE_ARC_RELEASE(_apiKey); _apiKey = apiKey; - _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:_instanceName]); + _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:_instanceName apiKey:_apiKey]); [self runOnBackgroundQueue:^{ // update database if necessary @@ -1563,8 +1563,8 @@ - (BOOL)upgradePrefs NSString *oldEventsDataDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *oldPropertyListPath = [oldEventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.plist"]; NSString *oldEventsDataPath = [oldEventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.archiveDict"]; - BOOL success = [self moveFileIfNotExists:oldPropertyListPath to:_propertyListPath]; - success &= [self moveFileIfNotExists:oldEventsDataPath to:_eventsDataPath]; + BOOL success = [AMPUtils moveFileIfNotExists:oldPropertyListPath to:_propertyListPath]; + success &= [AMPUtils moveFileIfNotExists:oldEventsDataPath to:_eventsDataPath]; return success; } @@ -1649,22 +1649,5 @@ - (BOOL)archive:(id) obj toFile:(NSString*)path { return [NSKeyedArchiver archiveRootObject:obj toFile:path]; } -- (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error; - if (![fileManager fileExistsAtPath:to] && - [fileManager fileExistsAtPath:from]) { - if ([fileManager copyItemAtPath:from toPath:to error:&error]) { - AMPLITUDE_LOG(@"INFO: copied %@ to %@", from, to); - [fileManager removeItemAtPath:from error:NULL]; - } else { - AMPLITUDE_LOG(@"WARN: Copy from %@ to %@ failed: %@", from, to, error); - return NO; - } - } - return YES; -} - #pragma clang diagnostic pop @end diff --git a/AmplitudeTests/AMPDatabaseHelperTests.m b/AmplitudeTests/AMPDatabaseHelperTests.m index bf5f6093..9d0c7bb8 100644 --- a/AmplitudeTests/AMPDatabaseHelperTests.m +++ b/AmplitudeTests/AMPDatabaseHelperTests.m @@ -17,9 +17,15 @@ @interface AMPDatabaseHelperTests : XCTestCase @implementation AMPDatabaseHelperTests {} + +NSString *const testApiKey = @"000000"; +NSString *const apiKey1 = @"111111"; +NSString *const apiKey2 = @"222222"; + + - (void)setUp { [super setUp]; - self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper]; + self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; [self.databaseHelper resetDB:NO]; } @@ -31,23 +37,22 @@ - (void)tearDown { - (void)testGetDatabaseHelper { // test backwards compatibility on default instance - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:nil]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:@""]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:kAMPDefaultInstance]); + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:@"" apiKey:testApiKey]); + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:kAMPDefaultInstance apiKey:testApiKey]); - AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a"]; - AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b"]; + AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]; + AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]; XCTAssertNotEqual(dbHelper, a); XCTAssertNotEqual(dbHelper, b); XCTAssertNotEqual(a, b); - XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"a"]); - XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"b"]); + XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]); + XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]); // test case insensitive instance name - XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"A"]); - XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"B"]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:[kAMPDefaultInstance uppercaseString]]); + XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"A" apiKey:apiKey1]); + XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"B" apiKey:apiKey2]); + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:[kAMPDefaultInstance uppercaseString] apiKey:testApiKey]); // test each instance maintains separate database files XCTAssertTrue([a.databasePath rangeOfString:@"com.amplitude.database_a"].location != NSNotFound); @@ -60,9 +65,9 @@ - (void)testGetDatabaseHelper { } - (void)testSeparateInstances { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a"]; - AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b"]; + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; + AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]; + AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]; [a resetDB:NO]; [b resetDB:NO]; diff --git a/AmplitudeTests/Amplitude+Test.m b/AmplitudeTests/Amplitude+Test.m index 86a9ec71..67dd5d82 100644 --- a/AmplitudeTests/Amplitude+Test.m +++ b/AmplitudeTests/Amplitude+Test.m @@ -19,6 +19,8 @@ @implementation Amplitude (Test) @dynamic sessionId; @dynamic lastEventTime; +NSString *const newTestApiKey = @"000000"; + - (void)flushQueue { [self flushQueueWithQueue:[self backgroundQueue]]; } @@ -28,22 +30,22 @@ - (void)flushQueueWithQueue:(NSOperationQueue*) queue { } - (NSDictionary *)getEvent:(NSInteger) fromEnd { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEvents:-1 limit:-1]; return [events objectAtIndex:[events count] - fromEnd - 1]; } - (NSDictionary *)getLastEvent { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEvents:-1 limit:-1]; return [events lastObject]; } - (NSDictionary *)getLastIdentify { - NSArray *identifys = [[AMPDatabaseHelper getDatabaseHelper] getIdentifys:-1 limit:-1]; + NSArray *identifys = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getIdentifys:-1 limit:-1]; return [identifys lastObject]; } - (NSUInteger)queuedEventCount { - return [[AMPDatabaseHelper getDatabaseHelper] getEventCount]; + return [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEventCount]; } - (void)flushUploads:(void (^)())handler { diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 38129363..64279647 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -100,9 +100,9 @@ - (void)testSeparateInstancesLogEventsSeparate { NSString *newInstance2 = @"newApp2"; NSString *newApiKey2 = @"0987654321"; - AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getDatabaseHelper]; - AMPDatabaseHelper *newDBHelper1 = [AMPDatabaseHelper getDatabaseHelper:newInstance1]; - AMPDatabaseHelper *newDBHelper2 = [AMPDatabaseHelper getDatabaseHelper:newInstance2]; + AMPDatabaseHelper *oldDbHelper = self.databaseHelper; + AMPDatabaseHelper *newDBHelper1 = [AMPDatabaseHelper getDatabaseHelper:newInstance1 apiKey:newApiKey1]; + AMPDatabaseHelper *newDBHelper2 = [AMPDatabaseHelper getDatabaseHelper:newInstance2 apiKey:newApiKey2]; // reset databases [oldDbHelper resetDB:NO]; @@ -186,7 +186,7 @@ - (void)testInitializeLoadUserIdFromEventData { XCTAssertEqual([client userId], nil); NSString *testUserId = @"testUserId"; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName]; + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; [dbHelper insertOrReplaceKeyValue:@"user_id" value:testUserId]; [client initializeApiKey:apiKey]; [client flushQueue]; @@ -201,7 +201,7 @@ - (void)testInitializeWithNilUserId { [self.amplitude initializeApiKey:apiKey userId:nilUserId]; [self.amplitude flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); - XCTAssertNil([[AMPDatabaseHelper getDatabaseHelper] getValue:@"user_id"]); + XCTAssertNil([self.databaseHelper getValue:@"user_id"]); } - (void)testInitializeWithUserId { @@ -305,7 +305,7 @@ - (void)testUUIDInEvent { [self.amplitude flushQueue]; XCTAssertEqual([self.amplitude queuedEventCount], 2); - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [self.databaseHelper getEvents:-1 limit:-1]; XCTAssertEqual(2, [[events[1] objectForKey:@"event_id"] intValue]); XCTAssertNotNil([events[0] objectForKey:@"uuid"]); XCTAssertNotNil([events[1] objectForKey:@"uuid"]); @@ -313,16 +313,15 @@ - (void)testUUIDInEvent { } - (void)testIdentify { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:2]; AMPIdentify *identify = [[AMPIdentify identify] set:@"key1" value:@"value1"]; [self.amplitude identify:identify]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *operations = [NSDictionary dictionaryWithObject:@"value1" forKey:@"key1"]; NSDictionary *expected = [NSDictionary dictionaryWithObject:operations forKey:@"$set"]; @@ -342,22 +341,20 @@ - (void)testIdentify { SAFE_ARC_RELEASE(identify2); [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } - (void)testLogRevenueV2 { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - // ignore invalid revenue objects [self.amplitude logRevenueV2:nil]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); [self.amplitude logRevenueV2:[AMPRevenue revenue]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); // log valid revenue object NSNumber *price = [NSNumber numberWithDouble:15.99]; @@ -370,7 +367,7 @@ - (void)testLogRevenueV2 { [self.amplitude logRevenueV2:revenue]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); NSDictionary *event = [self.amplitude getLastEvent]; XCTAssertEqualObjects([event objectForKey:@"event_type"], @"revenue_amount"); @@ -405,7 +402,6 @@ - (void) test{ } - (void)testMergeEventsAndIdentifys { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:7]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @@ -421,14 +417,14 @@ - (void)testMergeEventsAndIdentifys { [self.amplitude identify:[[AMPIdentify identify] set:@"gender" value:@"male"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 4); - XCTAssertEqual([dbHelper getIdentifyCount], 2); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 4); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 2); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); // verify merging - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; - NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[dbHelper getTotalEventCount]]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; + NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[self.databaseHelper getTotalEventCount]]; NSArray *mergedEvents = [merged objectForKey:@"events"]; XCTAssertEqual(4, [[merged objectForKey:@"max_event_id"] intValue]); @@ -464,13 +460,12 @@ - (void)testMergeEventsAndIdentifys { [self.amplitude identify:[[AMPIdentify identify] unset:@"karma"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testMergeEventsBackwardsCompatible { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude identify:[[AMPIdentify identify] unset:@"key"]]; [self.amplitude logEvent:@"test_event"]; [self.amplitude flushQueue]; @@ -479,16 +474,16 @@ -(void)testMergeEventsBackwardsCompatible { NSMutableDictionary *event = [NSMutableDictionary dictionaryWithDictionary:[self.amplitude getLastEvent]]; [event removeObjectForKey:@"sequence_number"]; long eventId = [[event objectForKey:@"event_id"] longValue]; - [dbHelper removeEvent:eventId]; + [self.databaseHelper removeEvent:eventId]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event options:0 error:NULL]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - [dbHelper addEvent:jsonString]; + [self.databaseHelper addEvent:jsonString]; SAFE_ARC_RELEASE(jsonString); // the event without sequence number should be ordered before the identify - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; - NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[dbHelper getTotalEventCount]]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; + NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[self.databaseHelper getTotalEventCount]]; NSArray *mergedEvents = [merged objectForKey:@"events"]; XCTAssertEqualObjects([mergedEvents[0] objectForKey:@"event_type"], @"test_event"); XCTAssertNil([mergedEvents[0] objectForKey:@"sequence_number"]); @@ -556,16 +551,14 @@ -(void)testTruncateEventAndIdentify { } -(void)testAutoIncrementSequenceNumber { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; int limit = 10; for (int i = 0; i < limit; i++) { XCTAssertEqual([self.amplitude getNextSequenceNumber], i+1); - XCTAssertEqual([[dbHelper getLongValue:@"sequence_number"] intValue], i+1); + XCTAssertEqual([[self.databaseHelper getLongValue:@"sequence_number"] intValue], i+1); } } -(void)testSetOffline { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] @@ -578,23 +571,22 @@ -(void)testSetOffline { [self.amplitude identify:[[AMPIdentify identify] set:@"key" value:@"value"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 2); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 3); + XCTAssertEqual([self.databaseHelper getEventCount], 2); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 3); [self.amplitude setOffline:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testSetOfflineTruncate { int eventMaxCount = 3; self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] @@ -610,25 +602,25 @@ -(void)testSetOfflineTruncate { [self.amplitude identify:[[AMPIdentify identify] unset:@"key3"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 3); - XCTAssertEqual([dbHelper getIdentifyCount], 3); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 3); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 3); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); [self.amplitude logEvent:@"test4"]; [self.amplitude identify:[[AMPIdentify identify] unset:@"key4"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 3); - XCTAssertEqual([dbHelper getIdentifyCount], 3); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 3); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 3); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; XCTAssertEqual([events count], 3); XCTAssertEqualObjects([events[0] objectForKey:@"event_type"], @"test2"); XCTAssertEqualObjects([events[1] objectForKey:@"event_type"], @"test3"); XCTAssertEqualObjects([events[2] objectForKey:@"event_type"], @"test4"); - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; XCTAssertEqual([identifys count], 3); XCTAssertEqualObjects([[[identifys[0] objectForKey:@"user_properties"] objectForKey:@"$unset"] objectForKey:@"key2"], @"-"); XCTAssertEqualObjects([[[identifys[1] objectForKey:@"user_properties"] objectForKey:@"$unset"] objectForKey:@"key3"], @"-"); @@ -638,9 +630,9 @@ -(void)testSetOfflineTruncate { [self.amplitude setOffline:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testTruncateEventsQueues { @@ -648,31 +640,29 @@ -(void)testTruncateEventsQueues { XCTAssertGreaterThanOrEqual(eventMaxCount, kAMPEventRemoveBatchSize); self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setOffline:YES]; for (int i = 0; i < eventMaxCount; i++) { [self.amplitude logEvent:@"test"]; } [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount - (eventMaxCount/10) + 1); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount - (eventMaxCount/10) + 1); } -(void)testTruncateEventsQueuesWithOneEvent { int eventMaxCount = 1; self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude logEvent:@"test1"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); [self.amplitude logEvent:@"test2"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); NSDictionary *event = [self.amplitude getLastEvent]; XCTAssertEqualObjects([event objectForKey:@"event_type"], @"test2"); @@ -683,19 +673,18 @@ -(void)testInvalidJSONEventProperties { NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:url, url, url, @"url", nil]; [self.amplitude logEvent:@"test" withEventProperties:properties]; [self.amplitude flushQueue]; - XCTAssertEqual([[AMPDatabaseHelper getDatabaseHelper] getEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); } -(void)testClearUserProperties { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:2]; [self.amplitude clearUserProperties]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expected = [NSDictionary dictionaryWithObject:@"-" forKey:@"$clearAll"]; NSDictionary *event = [self.amplitude getLastIdentify]; @@ -706,13 +695,12 @@ -(void)testClearUserProperties { } -(void)testSetGroup { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setGroup:@"orgId" groupName:[NSNumber numberWithInt:15]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *groups = [NSDictionary dictionaryWithObject:@"15" forKey:@"orgId"]; NSDictionary *userProperties = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:15] forKey:@"orgId"] forKey:@"$set"]; @@ -725,7 +713,6 @@ -(void)testSetGroup { } -(void)testLogEventWithGroups { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *groups = [NSMutableDictionary dictionary]; [groups setObject:[NSNumber numberWithInt: 10] forKey:[NSNumber numberWithFloat: 1.23]]; // validateGroups should coerce non-string values into strings @@ -737,9 +724,9 @@ -(void)testLogEventWithGroups { [self.amplitude logEvent:@"test" withEventProperties:nil withGroups:groups outOfSession:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 1); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expectedGroups = [NSDictionary dictionaryWithObjectsAndKeys:@"10", @"1.23", @[@"test2", @"0"], @"array", nil]; diff --git a/AmplitudeTests/BaseTestCase.m b/AmplitudeTests/BaseTestCase.m index f147ad0e..92f2c485 100644 --- a/AmplitudeTests/BaseTestCase.m +++ b/AmplitudeTests/BaseTestCase.m @@ -26,7 +26,7 @@ @implementation BaseTestCase { - (void)setUp { [super setUp]; self.amplitude = [Amplitude alloc]; - self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper]; + self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:apiKey]; XCTAssertTrue([self.databaseHelper resetDB:NO]); [self.amplitude init]; diff --git a/AmplitudeTests/SessionTests.m b/AmplitudeTests/SessionTests.m index 7abeb2e6..92720c9d 100644 --- a/AmplitudeTests/SessionTests.m +++ b/AmplitudeTests/SessionTests.m @@ -215,18 +215,16 @@ - (void)testSessionEventsOn32BitDevices { } - (void)testSkipSessionCheckWhenLoggingSessionEvents { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; NSNumber *timestamp = [NSNumber numberWithLongLong:[date timeIntervalSince1970] * 1000]; - [dbHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; + [self.databaseHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; self.amplitude.trackingSessionEvents = YES; [self.amplitude initializeApiKey:apiKey userId:nil]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 2); - NSArray *events = [dbHelper getEvents:-1 limit:2]; + XCTAssertEqual([self.databaseHelper getEventCount], 2); + NSArray *events = [self.databaseHelper getEvents:-1 limit:2]; XCTAssertEqualObjects(events[0][@"event_type"], kAMPSessionEndEvent); XCTAssertEqualObjects(events[1][@"event_type"], kAMPSessionStartEvent); } diff --git a/AmplitudeTests/SetupTests.m b/AmplitudeTests/SetupTests.m index b843975a..e6f08ce7 100644 --- a/AmplitudeTests/SetupTests.m +++ b/AmplitudeTests/SetupTests.m @@ -79,8 +79,7 @@ - (void)testOptOut { - (void)testUserPropertiesSet { [self.amplitude initializeApiKey:apiKey]; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - XCTAssertEqual([dbHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); NSDictionary *properties = @{ @"shoeSize": @10, @@ -90,9 +89,9 @@ - (void)testUserPropertiesSet { [self.amplitude setUserProperties:properties]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expected = [NSDictionary dictionaryWithObject:properties forKey:AMP_OP_SET]; @@ -104,42 +103,40 @@ - (void)testUserPropertiesSet { } - (void)testSetDeviceId { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - [self.amplitude initializeApiKey:apiKey]; [self.amplitude flushQueue]; NSString *generatedDeviceId = [self.amplitude getDeviceId]; XCTAssertNotNil(generatedDeviceId); XCTAssertEqual(generatedDeviceId.length, 36); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); // test setting invalid device ids [self.amplitude setDeviceId:nil]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); id dict = [NSDictionary dictionary]; [self.amplitude setDeviceId:dict]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); [self.amplitude setDeviceId:@"e3f5536a141811db40efd6400f1d0a4e"]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); [self.amplitude setDeviceId:@"04bab7ee75b9a58d39b8dc54e8851084"]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); NSString *validDeviceId = [AMPUtils generateUUID]; [self.amplitude setDeviceId:validDeviceId]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], validDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], validDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], validDeviceId); } @end From 6df395517a4e90ea41f8686148a0b5aae9eee024 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Thu, 11 Aug 2016 16:09:36 -0700 Subject: [PATCH 3/7] migrate db file and add tests --- Amplitude/AMPDatabaseHelper.h | 1 + Amplitude/AMPDatabaseHelper.m | 18 ++++++++++++------ AmplitudeTests/AMPDatabaseHelperTests.m | 21 ++++++++++++++++++++- AmplitudeTests/AmplitudeTests.m | 19 +++++++++++++++++++ CHANGELOG.md | 1 + 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Amplitude/AMPDatabaseHelper.h b/Amplitude/AMPDatabaseHelper.h index eab5378d..5c6dd781 100644 --- a/Amplitude/AMPDatabaseHelper.h +++ b/Amplitude/AMPDatabaseHelper.h @@ -11,6 +11,7 @@ @property (nonatomic, strong, readonly) NSString *databasePath; + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey; ++ (AMPDatabaseHelper*)getDatabaseHelperWithInstanceName:(NSString*) instanceName; // for testing only - (BOOL)createTables; - (BOOL)dropTables; - (BOOL)upgrade:(int) oldVersion newVersion:(int) newVersion; diff --git a/Amplitude/AMPDatabaseHelper.m b/Amplitude/AMPDatabaseHelper.m index bd668b3a..8a63ef07 100644 --- a/Amplitude/AMPDatabaseHelper.m +++ b/Amplitude/AMPDatabaseHelper.m @@ -31,7 +31,6 @@ @interface AMPDatabaseHelper() @implementation AMPDatabaseHelper { - BOOL _databaseCreated; sqlite3 *_database; dispatch_queue_t _queue; } @@ -95,6 +94,13 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSStrin return dbHelper; } +// for testing only ++ (AMPDatabaseHelper*)getDatabaseHelperWithInstanceName:(NSString*) instanceName +{ + AMPDatabaseHelper *dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName andApiKey:nil]; + return SAFE_ARC_AUTORELEASE(dbHelper); +} + // instanceName should not be null, getDatabaseHelper will guard // apiKey should only be null for testing - Amplitude client will guard - (id)initWithInstanceName:(NSString*) instanceName andApiKey:(NSString*) apiKey @@ -107,11 +113,11 @@ - (id)initWithInstanceName:(NSString*) instanceName andApiKey:(NSString*) apiKey } // migrate to new db filename -// if (![AMPUtils isEmptyString:apiKey]) { -// NSString *newDatabasePath = [NSString stringWithFormat:@"%@_%@", databasePath, apiKey]; -//// [AMPUtils moveFileIfNotExists:databasePath to:newDatabasePath]; -//// databasePath = newDatabasePath; -// } + if (![AMPUtils isEmptyString:apiKey]) { + NSString *newDatabasePath = [NSString stringWithFormat:@"%@_%@", databasePath, apiKey]; + [AMPUtils moveFileIfNotExists:databasePath to:newDatabasePath]; + databasePath = newDatabasePath; + } _databasePath = SAFE_ARC_RETAIN(databasePath); _queue = dispatch_queue_create([QUEUE_NAME UTF8String], NULL); diff --git a/AmplitudeTests/AMPDatabaseHelperTests.m b/AmplitudeTests/AMPDatabaseHelperTests.m index 9d0c7bb8..1e21245e 100644 --- a/AmplitudeTests/AMPDatabaseHelperTests.m +++ b/AmplitudeTests/AMPDatabaseHelperTests.m @@ -35,6 +35,22 @@ - (void)tearDown { self.databaseHelper = nil; } +- (void)testScopeMigration { + // use separate instance/apiKey from test default to prevent overlap of data + NSString *instanceName = @"migrationInstance"; + NSString *apiKey = @"migrationApiKey"; + + // initialize dbHelper with old filename + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelperWithInstanceName:instanceName]; + [dbHelper insertOrReplaceKeyValue:@"migrationTestKey" value:@"migrationTestValue"]; + + // force migration + AMPDatabaseHelper *newDbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; + XCTAssertEqualObjects([newDbHelper getValue:@"migrationTestKey"], @"migrationTestValue"); + + [newDbHelper deleteDB]; +} + - (void)testGetDatabaseHelper { // test backwards compatibility on default instance AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; @@ -56,9 +72,12 @@ - (void)testGetDatabaseHelper { // test each instance maintains separate database files XCTAssertTrue([a.databasePath rangeOfString:@"com.amplitude.database_a"].location != NSNotFound); + XCTAssertTrue([a.databasePath rangeOfString:@"com.amplitude.database_a_111111"].location != NSNotFound); XCTAssertTrue([b.databasePath rangeOfString:@"com.amplitude.database_b"].location != NSNotFound); + XCTAssertTrue([b.databasePath rangeOfString:@"com.amplitude.database_b_222222"].location != NSNotFound); XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database"].location != NSNotFound); - XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_"].location == NSNotFound); + XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_$default_instance"].location == NSNotFound); + XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_000000"].location != NSNotFound); [a deleteDB]; [b deleteDB]; diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 64279647..56901c46 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -226,6 +226,25 @@ - (void)testSkipReinitialization { XCTAssertEqual([self.amplitude userId], nil); } +- (void)testDatabaseScopeMigration { + NSString *migrationInstanceName = @"testMigrationInstance"; + NSString *migrationApiKey = @"testMigrationInstanceApiKey"; + NSString *deviceId = @"testMigrationDeviceId"; + + // create old database file + AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getDatabaseHelperWithInstanceName:migrationInstanceName]; + [oldDbHelper insertOrReplaceKeyValue:@"device_id" value:deviceId]; + + // init new client, verify migration + Amplitude *client = [Amplitude instanceWithName:migrationInstanceName]; + [client initializeApiKey:migrationApiKey]; + [client flushQueue]; + XCTAssertEqualObjects([client getDeviceId], deviceId); + + AMPDatabaseHelper *newDbHelper = [AMPDatabaseHelper getDatabaseHelper:migrationInstanceName apiKey:migrationApiKey]; + XCTAssertEqualObjects([newDbHelper getValue:@"device_id"], deviceId); +} + - (void)testClearUserId { [self.amplitude flushQueue]; XCTAssertEqual([self.amplitude userId], nil); diff --git a/CHANGELOG.md b/CHANGELOG.md index 82bf49f1..c2479760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased +* Migrate Sqlite database file to new filename with apiKey. * Migrate all database logic from `init` to `initializeApiKey`. * Run `init` logic on `backgroundQueue`, removing need for separate `initializerQueue`. From 94f30b7a2919e6631c3b2a80adbfbb01e69a8c53 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Tue, 16 Aug 2016 23:41:27 -0700 Subject: [PATCH 4/7] change api, added setApiKey and initialize methods, updated tests --- Amplitude/AMPUtils.m | 7 +- Amplitude/Amplitude.h | 23 +----- Amplitude/Amplitude.m | 137 ++++++++++++------------------- AmplitudeTests/Amplitude+Test.h | 2 +- AmplitudeTests/Amplitude+Test.m | 2 +- AmplitudeTests/AmplitudeTests.m | 24 +++--- AmplitudeTests/SSLPinningTests.m | 6 +- AmplitudeTests/SessionTests.m | 20 +++-- AmplitudeTests/SetupTests.m | 20 +++-- 9 files changed, 98 insertions(+), 143 deletions(-) diff --git a/Amplitude/AMPUtils.m b/Amplitude/AMPUtils.m index c7886159..821712e4 100644 --- a/Amplitude/AMPUtils.m +++ b/Amplitude/AMPUtils.m @@ -147,11 +147,10 @@ + (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to NSError *error; if (![fileManager fileExistsAtPath:to] && [fileManager fileExistsAtPath:from]) { - if ([fileManager copyItemAtPath:from toPath:to error:&error]) { - AMPLITUDE_LOG(@"INFO: copied %@ to %@", from, to); - [fileManager removeItemAtPath:from error:NULL]; + if ([fileManager moveItemAtPath:from toPath:to error:&error]) { + AMPLITUDE_LOG(@"INFO: moved %@ to %@", from, to); } else { - AMPLITUDE_LOG(@"WARN: Copy from %@ to %@ failed: %@", from, to, error); + AMPLITUDE_LOG(@"WARN: Move from %@ to %@ failed: %@", from, to, error); return NO; } } diff --git a/Amplitude/Amplitude.h b/Amplitude/Amplitude.h index 0c489f04..6ac822e3 100644 --- a/Amplitude/Amplitude.h +++ b/Amplitude/Amplitude.h @@ -139,21 +139,8 @@ @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings */ -- (void)initializeApiKey:(NSString*) apiKey; - -/** - Initializes the Amplitude instance with your Amplitude API key and sets a user identifier for the current user. - - We recommend you first initialize your class within your "didFinishLaunchingWithOptions" method inside your app delegate. - - **Note:** this is required before you can log any events. - - @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings - - @param userId If your app has its own login system that you want to track users with, you can set the userId. - -*/ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId; +- (Amplitude *)setApiKey:(NSString*) apiKey; +- (void)initialize; /**----------------------------------------------------------------------------- @@ -490,14 +477,8 @@ #pragma mark - Deprecated methods -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId startSession:(BOOL)startSession __attribute((deprecated())); - - (void)startSession __attribute((deprecated())); -+ (void)initializeApiKey:(NSString*) apiKey __attribute((deprecated())); - -+ (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId __attribute((deprecated())); - + (void)logEvent:(NSString*) eventType __attribute((deprecated())); + (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) eventProperties __attribute((deprecated())); diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 3c71a0cb..aa70c107 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -50,7 +50,7 @@ @interface Amplitude () @property (nonatomic, strong) NSOperationQueue *backgroundQueue; @property (nonatomic, strong) AMPDatabaseHelper *dbHelper; -@property (nonatomic, assign) BOOL initialized; +@property (nonatomic, assign) BOOL initializedDatabase; @property (nonatomic, assign) BOOL sslPinningEnabled; @property (nonatomic, assign) long long sessionId; @@ -130,14 +130,6 @@ + (Amplitude *)instanceWithName:(NSString*)instanceName { return client; } -+ (void)initializeApiKey:(NSString*) apiKey { - [[Amplitude instance] initializeApiKey:apiKey]; -} - -+ (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId { - [[Amplitude instance] initializeApiKey:apiKey userId:userId]; -} - + (void)logEvent:(NSString*) eventType { [[Amplitude instance] logEvent:eventType]; } @@ -216,7 +208,7 @@ - (id)initWithInstanceName:(NSString*) instanceName _sslPinningEnabled = NO; #endif - _initialized = NO; + _initializedDatabase = NO; _locationListeningEnabled = YES; _sessionId = -1; _updateScheduled = NO; @@ -252,7 +244,10 @@ - (id)initWithInstanceName:(NSString*) instanceName } _propertyListPath = SAFE_ARC_RETAIN(propertyListPath); _eventsDataPath = SAFE_ARC_RETAIN([eventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.archiveDict"]); - [self upgradePrefs]; // migrate legacy prefs file + + if ([_instanceName isEqualToString:kAMPDefaultInstance]) { + [self upgradePrefs]; // migrate legacy prefs file for the default instance + } // Load propertyList object, which contains current db version _propertyList = SAFE_ARC_RETAIN([self deserializePList:_propertyListPath]); @@ -381,43 +376,23 @@ - (void) dealloc { SAFE_ARC_SUPER_DEALLOC(); } -- (void)initializeApiKey:(NSString*) apiKey -{ - [self initializeApiKey:apiKey userId:nil setUserId: NO]; -} - -/** - * Initialize Amplitude with a given apiKey and userId. - */ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId -{ - [self initializeApiKey:apiKey userId:userId setUserId: YES]; -} - -/** - * SetUserId: client explicitly initialized with a userId (can be nil). - * If setUserId is NO, then attempt to load userId from saved eventsData. - */ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId:(BOOL) setUserId +- (Amplitude *)setApiKey:(NSString*) apiKey { if (apiKey == nil) { AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil in initializeApiKey:"); - return; + return self; } if (![self isArgument:apiKey validType:[NSString class] methodName:@"initializeApiKey:"]) { - return; - } - if (userId != nil && ![self isArgument:userId validType:[NSString class] methodName:@"initializeApiKey:"]) { - return; + return self; } if ([apiKey length] == 0) { AMPLITUDE_ERROR(@"ERROR: apiKey cannot be blank in initializeApiKey:"); - return; + return self; } - if (!_initialized) { + if (!_initializedDatabase) { SAFE_ARC_RETAIN(apiKey); SAFE_ARC_RELEASE(_apiKey); _apiKey = apiKey; @@ -455,27 +430,40 @@ - (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId: _sessionId = previousSessionId; } [self initializeDeviceId]; - - if (setUserId) { - [self setUserId:userId]; - } else { - _userId = SAFE_ARC_RETAIN([self.dbHelper getValue:USER_ID]); - } + _userId = SAFE_ARC_RETAIN([self.dbHelper getValue:USER_ID]); }]; // now we can add observers after setting apikey since enterBackground saves timestamp to DB [self addObservers]; + _initializedDatabase = YES; + } - UIApplication *app = [self getSharedApplication]; - if (app != nil) { - UIApplicationState state = app.applicationState; - if (state != UIApplicationStateBackground) { - // If this is called while the app is running in the background, for example - // via a push notification, don't call enterForeground - [self enterForeground]; - } + return self; +} + +- (BOOL)checkApiKeyForMethod:(NSString*) methodName +{ + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with setApiKey before calling %@", methodName); + return NO; + } + return YES; +} + +- (void)initialize +{ + if (![self checkApiKeyForMethod:@"initialize"]) { + return; + } + + UIApplication *app = [self getSharedApplication]; + if (app != nil) { + UIApplicationState state = app.applicationState; + if (state != UIApplicationStateBackground) { + // If this is called while the app is running in the background, for example + // via a push notification, don't call enterForeground + [self enterForeground]; } - _initialized = YES; } } @@ -488,11 +476,6 @@ - (UIApplication *)getSharedApplication return nil; } -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId startSession:(BOOL)startSession -{ - [self initializeApiKey:apiKey userId:userId]; -} - /** * Run a block in the background. If already in the background, run immediately. */ @@ -538,8 +521,7 @@ - (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) event - (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) eventProperties withApiProperties:(NSDictionary*) apiProperties withUserProperties:(NSDictionary*) userProperties withGroups:(NSDictionary*) groups withTimestamp:(NSNumber*) timestamp outOfSession:(BOOL) outOfSession { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logEvent"); + if (![self checkApiKeyForMethod:@"logEvent"]) { return; } @@ -699,8 +681,7 @@ - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity p - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity price:(NSNumber*) price receipt:(NSData*) receipt { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logRevenue:"); + if (![self checkApiKeyForMethod:@"logRevenue"]) { return; } if (![self isArgument:price validType:[NSNumber class] methodName:@"logRevenue:"]) { @@ -726,8 +707,7 @@ - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity p - (void)logRevenueV2:(AMPRevenue*) revenue { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logRevenueV2"); + if (![self checkApiKeyForMethod:@"logRevenueV2"]) { return; } if (revenue == nil || ![revenue isValidRevenue]) { @@ -766,8 +746,7 @@ - (void)uploadEvents - (void)uploadEventsWithLimit:(int) limit { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling uploadEvents:"); + if (![self checkApiKeyForMethod:@"uploadEvents"]) { return; } @@ -1131,11 +1110,6 @@ - (void)startNewSession:(NSNumber*) timestamp - (void)sendSessionEvent:(NSString*) sessionEvent { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before sending session event"); - return; - } - if (![self inSession]) { return; } @@ -1211,8 +1185,7 @@ - (void)startSession - (void)identify:(AMPIdentify *)identify { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling identify"); + if (![self checkApiKeyForMethod:@"identify"]) { return; } @@ -1226,8 +1199,7 @@ - (void)identify:(AMPIdentify *)identify - (void)setUserProperties:(NSDictionary*) userProperties { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setUserProperties"); + if (![self checkApiKeyForMethod:@"setUserProperties"]) { return; } @@ -1255,19 +1227,16 @@ - (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace - (void)clearUserProperties { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling clearUserProperties"); + if (![self checkApiKeyForMethod:@"clearUserProperties"]) { return; } - AMPIdentify *identify = [[AMPIdentify identify] clearAll]; [self identify:identify]; } - (void)setGroup:(NSString *)groupType groupName:(NSObject *)groupName { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setGroupType"); + if (![self checkApiKeyForMethod:@"setGroup"]) { return; } @@ -1284,8 +1253,7 @@ - (void)setGroup:(NSString *)groupType groupName:(NSObject *)groupName - (void)setUserId:(NSString*) userId { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setUserId"); + if (![self checkApiKeyForMethod:@"setUserId"]) { return; } @@ -1303,8 +1271,7 @@ - (void)setUserId:(NSString*) userId - (void)setOptOut:(BOOL)enabled { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setOptOut"); + if (![self checkApiKeyForMethod:@"setOptOut"]) { return; } @@ -1331,8 +1298,7 @@ - (void)setEventUploadMaxBatchSize:(int) eventUploadMaxBatchSize - (BOOL)optOut { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before toggling and fetching optOut"); + if (![self checkApiKeyForMethod:@"optOut"]) { return NO; } return [[self.dbHelper getLongValue:OPT_OUT] boolValue]; @@ -1340,8 +1306,7 @@ - (BOOL)optOut - (void)setDeviceId:(NSString*)deviceId { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setDeviceId"); + if (![self checkApiKeyForMethod:@"setDeviceId"]) { return; } diff --git a/AmplitudeTests/Amplitude+Test.h b/AmplitudeTests/Amplitude+Test.h index 94831185..1be25d52 100644 --- a/AmplitudeTests/Amplitude+Test.h +++ b/AmplitudeTests/Amplitude+Test.h @@ -13,7 +13,7 @@ @property (nonatomic, strong) NSOperationQueue *backgroundQueue; @property (nonatomic, strong) NSOperationQueue *initializerQueue; @property (nonatomic, strong) NSMutableDictionary *eventsData; -@property (nonatomic, assign) BOOL initialized; +@property (nonatomic, assign) BOOL initializedDatabase; @property (nonatomic, assign) long long sessionId; @property (nonatomic, strong) NSNumber* lastEventTime; diff --git a/AmplitudeTests/Amplitude+Test.m b/AmplitudeTests/Amplitude+Test.m index 67dd5d82..a5c51cba 100644 --- a/AmplitudeTests/Amplitude+Test.m +++ b/AmplitudeTests/Amplitude+Test.m @@ -15,7 +15,7 @@ @implementation Amplitude (Test) @dynamic backgroundQueue; @dynamic initializerQueue; @dynamic eventsData; -@dynamic initialized; +@dynamic initializedDatabase; @dynamic sessionId; @dynamic lastEventTime; diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 56901c46..71ba065f 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -36,7 +36,7 @@ - (void)setUp { [super setUp]; _connectionMock = [OCMockObject mockForClass:NSURLConnection.class]; _connectionCallCount = 0; - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; } - (void)tearDown { @@ -110,7 +110,7 @@ - (void)testSeparateInstancesLogEventsSeparate { [newDBHelper2 resetDB:NO]; // setup existing database file, init default instance - [[Amplitude instance] initializeApiKey:apiKey]; + [[[Amplitude instance] setApiKey:apiKey] initialize]; [[Amplitude instance] flushQueue]; [oldDbHelper insertOrReplaceKeyLongValue:@"sequence_number" value:[NSNumber numberWithLongLong:1000]]; [oldDbHelper addEvent:@"{\"event_type\":\"oldEvent\"}"]; @@ -130,7 +130,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertNil([newDBHelper2 getLongValue:@"sequence_number"]); // init first new app and verify separate database - [[Amplitude instanceWithName:newInstance1] initializeApiKey:newApiKey1]; + [[[Amplitude instanceWithName:newInstance1] setApiKey:newApiKey1] initialize]; [[Amplitude instanceWithName:newInstance1] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], [newDBHelper1 getValue:@"device_id"]); @@ -139,7 +139,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertEqual([newDBHelper1 getIdentifyCount], 0); // init second new app and verify separate database - [[Amplitude instanceWithName:newInstance2] initializeApiKey:newApiKey2]; + [[[Amplitude instanceWithName:newInstance2] setApiKey:newApiKey2] initialize]; [[Amplitude instanceWithName:newInstance2] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], [newDBHelper2 getValue:@"device_id"]); @@ -188,7 +188,7 @@ - (void)testInitializeLoadUserIdFromEventData { NSString *testUserId = @"testUserId"; AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; [dbHelper insertOrReplaceKeyValue:@"user_id" value:testUserId]; - [client initializeApiKey:apiKey]; + [[client setApiKey:apiKey] initialize]; [client flushQueue]; XCTAssertTrue([[client userId] isEqualToString:testUserId]); } @@ -198,7 +198,8 @@ - (void)testInitializeWithNilUserId { XCTAssertEqual([self.amplitude userId], nil); NSString *nilUserId = nil; - [self.amplitude initializeApiKey:apiKey userId:nilUserId]; + [[self.amplitude setApiKey:apiKey] setUserId:nilUserId]; + [self.amplitude initialize]; [self.amplitude flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); XCTAssertNil([self.databaseHelper getValue:@"user_id"]); @@ -211,7 +212,8 @@ - (void)testInitializeWithUserId { XCTAssertEqual([client userId], nil); NSString *testUserId = @"testUserId"; - [client initializeApiKey:apiKey userId:testUserId]; + [[client setApiKey:apiKey] setUserId:testUserId]; + [client initialize]; [client flushQueue]; XCTAssertEqual([client userId], testUserId); } @@ -221,9 +223,11 @@ - (void)testSkipReinitialization { XCTAssertEqual([self.amplitude userId], nil); NSString *testUserId = @"testUserId"; - [self.amplitude initializeApiKey:apiKey userId:testUserId]; + [[self.amplitude setApiKey:apiKey] setUserId:testUserId]; + [self.amplitude initialize]; [self.amplitude flushQueue]; - XCTAssertEqual([self.amplitude userId], nil); + // since setUserId is called explicitly now, will override existing value even though reinitialization skipped + XCTAssertEqualObjects([self.amplitude userId], testUserId); } - (void)testDatabaseScopeMigration { @@ -237,7 +241,7 @@ - (void)testDatabaseScopeMigration { // init new client, verify migration Amplitude *client = [Amplitude instanceWithName:migrationInstanceName]; - [client initializeApiKey:migrationApiKey]; + [[client setApiKey:migrationApiKey] initialize]; [client flushQueue]; XCTAssertEqualObjects([client getDeviceId], deviceId); diff --git a/AmplitudeTests/SSLPinningTests.m b/AmplitudeTests/SSLPinningTests.m index 62cdbaa3..63bd184c 100644 --- a/AmplitudeTests/SSLPinningTests.m +++ b/AmplitudeTests/SSLPinningTests.m @@ -40,7 +40,7 @@ - (void)testSSLWithoutPinning { self.amplitude.sslPinningEnabled = NO; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test without SSL Pinning"]; [self.amplitude flushUploads:^() { NSDictionary *event = [self.amplitude getLastEvent]; @@ -61,7 +61,7 @@ - (void)testSSLPinningInvalidCert { self.amplitude.sslPinningEnabled = YES; [AMPURLConnection pinSSLCertificate:@[@"InvalidCertificationAuthority"]]; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test Invalid SSL Pinning"]; [self.amplitude flushUploads:^() { @@ -83,7 +83,7 @@ - (void)testSSLPinningValidCert { self.amplitude.sslPinningEnabled = YES; [AMPURLConnection pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test SSL Pinning"]; [self.amplitude flushUploads:^() { NSDictionary *event = [self.amplitude getLastEvent]; diff --git a/AmplitudeTests/SessionTests.m b/AmplitudeTests/SessionTests.m index 92720c9d..0c43cd8b 100644 --- a/AmplitudeTests/SessionTests.m +++ b/AmplitudeTests/SessionTests.m @@ -41,7 +41,7 @@ - (void)testSessionAutoStartedBackground { // mock amplitude object and verify enterForeground not called id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude reject] enterForeground]; - [mockAmplitude initializeApiKey:apiKey]; + [[mockAmplitude setApiKey:apiKey] initialize]; [mockAmplitude flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); @@ -54,7 +54,7 @@ - (void)testSessionAutoStartedInactive { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude expect] enterForeground]; - [mockAmplitude initializeApiKey:apiKey]; + [[mockAmplitude setApiKey:apiKey] initialize]; [mockAmplitude flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); @@ -67,7 +67,8 @@ - (void)testSessionHandling { NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; + [[mockAmplitude setApiKey:apiKey] setUserId:nil]; + [mockAmplitude initialize]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); XCTAssertEqual([mockAmplitude sessionId], 1000000); @@ -136,7 +137,8 @@ - (void)testSessionHandling { } - (void)testEnterBackgroundDoesNotTrackEvent { - [self.amplitude initializeApiKey:apiKey userId:nil]; + [[self.amplitude setApiKey:apiKey] setUserId:nil]; + [self.amplitude initialize]; [self.amplitude flushQueue]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; @@ -152,7 +154,8 @@ - (void)testTrackSessionEvents { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; + [[mockAmplitude setApiKey:apiKey] setUserId:nil]; + [mockAmplitude initialize]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); @@ -187,7 +190,8 @@ - (void)testSessionEventsOn32BitDevices { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; + [[mockAmplitude setApiKey:apiKey] setUserId:nil]; + [mockAmplitude initialize]; [mockAmplitude flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); @@ -220,8 +224,8 @@ - (void)testSkipSessionCheckWhenLoggingSessionEvents { [self.databaseHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; self.amplitude.trackingSessionEvents = YES; - [self.amplitude initializeApiKey:apiKey userId:nil]; - + [[self.amplitude setApiKey:apiKey] setUserId:nil]; + [self.amplitude initialize]; [self.amplitude flushQueue]; XCTAssertEqual([self.databaseHelper getEventCount], 2); NSArray *events = [self.databaseHelper getEvents:-1 limit:2]; diff --git a/AmplitudeTests/SetupTests.m b/AmplitudeTests/SetupTests.m index e6f08ce7..5cf5fc8e 100644 --- a/AmplitudeTests/SetupTests.m +++ b/AmplitudeTests/SetupTests.m @@ -30,12 +30,12 @@ - (void)tearDown { } - (void)testApiKeySet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; XCTAssertEqual(self.amplitude.apiKey, apiKey); } - (void)testDeviceIdSet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; XCTAssertNotNil([self.amplitude deviceId]); XCTAssertEqual([self.amplitude deviceId].length, 36); @@ -43,24 +43,26 @@ - (void)testDeviceIdSet { } - (void)testUserIdNotSet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; XCTAssertNil([self.amplitude userId]); } - (void)testUserIdSet { - [self.amplitude initializeApiKey:apiKey userId:userId]; + [[self.amplitude setApiKey:apiKey] setUserId:userId]; + [self.amplitude initialize]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude userId], userId); } - (void)testInitializedSet { - [self.amplitude initializeApiKey:apiKey]; - XCTAssert([self.amplitude initialized]); + [[self.amplitude setApiKey:apiKey] initialize]; + [self.amplitude flushQueue]; + XCTAssert([self.amplitude initializedDatabase]); } - (void)testOptOut { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude setOptOut:YES]; [self.amplitude logEvent:@"Opted Out"]; @@ -78,7 +80,7 @@ - (void)testOptOut { } - (void)testUserPropertiesSet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; XCTAssertEqual([self.databaseHelper getEventCount], 0); NSDictionary *properties = @{ @@ -103,7 +105,7 @@ - (void)testUserPropertiesSet { } - (void)testSetDeviceId { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; NSString *generatedDeviceId = [self.amplitude getDeviceId]; XCTAssertNotNil(generatedDeviceId); From 8df4911b5a90e2db298415efafea73249433969d Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Wed, 17 Aug 2016 21:09:58 -0700 Subject: [PATCH 5/7] update setters to return instance --- Amplitude/Amplitude.h | 90 ++++++++++++++++++++++++--------- Amplitude/Amplitude.m | 60 +++++++++++++--------- AmplitudeTests/AmplitudeTests.m | 33 ++++-------- AmplitudeTests/BaseTestCase.m | 3 +- AmplitudeTests/SessionTests.m | 29 +++-------- 5 files changed, 119 insertions(+), 96 deletions(-) diff --git a/Amplitude/Amplitude.h b/Amplitude/Amplitude.h index 6ac822e3..441b20de 100644 --- a/Amplitude/Amplitude.h +++ b/Amplitude/Amplitude.h @@ -14,14 +14,16 @@ Setup: 1. In every file that uses analytics, import Amplitude.h at the top `#import "Amplitude.h"` - 2. Be sure to initialize the API in your didFinishLaunchingWithOptions delegate `[[Amplitude instance] initializeApiKey:@"YOUR_API_KEY_HERE"];` - 3. Track an event anywhere in the app `[[Amplitude instance] logEvent:@"EVENT_IDENTIFIER_HERE"];` + 2. Be sure to set the API key and initialize the SDK in your app's didFinishLaunchingWithOptions delegate `[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] initialize];` + 3. Track an event anywhere in the app with `[[Amplitude instance] logEvent:@"EVENT_IDENTIFIER_HERE"];` 4. You can attach additional data to any event by passing a NSDictionary object: NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary]; [eventProperties setValue:@"VALUE_GOES_HERE" forKey:@"KEY_GOES_HERE"]; [[Amplitude instance] logEvent:@"Compute Hash" withEventProperties:eventProperties]; + 5. You can configure the SDK via configurable properties and public set methods such as `setUserId`. You can modify the instance properties at any time, for example `[Amplitude instance].trackingSessionEvents = YES;`. The public set methods should be called after setting the apiKey and before calling initialize. For example `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`, otherwise you can call them when appropriate, for example calling `setUserId` after the user logs in, etc. + **Note:** you should call SDK methods on an Amplitude instance, for example logging events with the default instance: `[[Amplitude instance] logEvent:@"testEvent"];` **Note:** the SDK supports tracking data to multiple Amplitude apps, via separate named instances. For example: `[[Amplitude instanceWithName:@"app1"] logEvent:@"testEvent"];` See [Tracking Events to Multiple Apps](https://github.com/amplitude/amplitude-ios#tracking-events-to-multiple-amplitude-apps). @@ -58,11 +60,6 @@ @property (nonatomic, strong, readonly) NSString *instanceName; @property (nonatomic ,strong, readonly) NSString *propertyListPath; -/** - Whether or to opt the current user out of tracking. If true then this blocks the logging of any events and properties, and blocks the sending of events to Amplitude servers. - */ -@property (nonatomic, assign) BOOL optOut; - /**----------------------------------------------------------------------------- * @name Configurable SDK thresholds and parameters @@ -126,21 +123,40 @@ + (Amplitude *)instanceWithName:(NSString*) instanceName; /**----------------------------------------------------------------------------- - * @name Initialize the Amplitude SDK with your Amplitude API Key + * @name Set your Amplitude API Key * ----------------------------------------------------------------------------- */ /** - Initializes the Amplitude instance with your Amplitude API key + Set your Amplitude API key in the Amplitude instance. - We recommend you first initialize your class within your "didFinishLaunchingWithOptions" method inside your app delegate. + We recommend you set the api key in your app's "didFinishLaunchingWithOptions" method inside your app delegate. **Note:** this is required before you can log any events. @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together like `[[[Amplitude instance] setApiKey:@"YOUR_API_KEY"] initialize];` */ - (Amplitude *)setApiKey:(NSString*) apiKey; -- (void)initialize; + +/**----------------------------------------------------------------------------- + * @name Initialize the SDK + * ----------------------------------------------------------------------------- + */ + +/** + After you set the API key call this to enable event tracking. + + **Note:** If you are configuring the SDK before tracking (either by modifying the configurable properties or calling any public set methods), do so before calling initialize. For example: `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`. + + **Note:** this is required before you can log any events. + + @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + */ +- (Amplitude *)initialize; /**----------------------------------------------------------------------------- @@ -314,9 +330,11 @@ @param userProperties An NSDictionary containing any additional data to be tracked. + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + @see [Setting Multiple Properties with setUserProperties](https://github.com/amplitude/Amplitude-iOS#setting-multiple-properties-with-setuserproperties) */ -- (void)setUserProperties:(NSDictionary*) userProperties; +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties; /** @@ -328,20 +346,24 @@ @param userProperties An NSDictionary containing any additional data to be tracked. @param replace This is deprecated. In earlier versions of this SDK, this replaced the in-memory userProperties dictionary with the input, but now userProperties are no longer stored in memory. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Multiple Properties with setUserProperties](https://github.com/amplitude/Amplitude-iOS#setting-multiple-properties-with-setuserproperties) */ -- (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace; +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace; /** Clears all properties that are tracked on the user level. **Note: the result is irreversible!** + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Clearing user properties](https://github.com/amplitude/Amplitude-iOS#clearing-user-properties-with-clearuserproperties) */ -- (void)clearUserProperties; +- (Amplitude *)clearUserProperties; /** Adds a user to a group or groups. You need to specify a groupType and groupName(s). @@ -353,13 +375,14 @@ **Note:** this will also set groupType: groupName as a user property. @param groupType You need to specify a group type (for example "orgId"). - @param groupName The value for the group name, can be a string or an array of strings, (for example for groupType orgId, the groupName would be the actual id number, like 15). + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Groups](https://github.com/amplitude/Amplitude-iOS#setting-groups) */ -- (void)setGroup:(NSString*) groupType groupName:(NSObject*) groupName; +- (Amplitude *)setGroup:(NSString*) groupType groupName:(NSObject*) groupName; /**----------------------------------------------------------------------------- * @name Setting User and Device Identifiers @@ -370,10 +393,12 @@ Sets the userId. @param userId If your app has its own login system that you want to track users with, you can set the userId. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Custom UserIds](https://github.com/amplitude/Amplitude-iOS#setting-custom-user-ids) */ -- (void)setUserId:(NSString*) userId; +- (Amplitude *)setUserId:(NSString*) userId; /** Sets the deviceId. @@ -381,10 +406,12 @@ **NOTE: not recommended unless you know what you are doing** @param deviceId If your app has its own system for tracking devices, you can set the deviceId. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Custom Device Ids](https://github.com/amplitude/Amplitude-iOS#custom-device-ids) */ -- (void)setDeviceId:(NSString*) deviceId; +- (Amplitude *)setDeviceId:(NSString*) deviceId; /**----------------------------------------------------------------------------- * @name Configuring the SDK instance @@ -397,8 +424,10 @@ If the user wants to opt out of all tracking, use this method to enable opt out for them. Once opt out is enabled, no events will be saved locally or sent to the server. Calling this method again with enabled set to NO will turn tracking back on for the user. @param enabled Whether tracking opt out should be enabled or disabled. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)setOptOut:(BOOL)enabled; +- (Amplitude *)setOptOut:(BOOL)enabled; /** Disables sending logged events to Amplitude servers. @@ -406,22 +435,28 @@ If you want to stop logged events from being sent to Amplitude severs, use this method to set the client to offline. Once offline is enabled, logged events will not be sent to the server until offline is disabled. Calling this method again with offline set to NO will allow events to be sent to server and the client will attempt to send events that have been queued while offline. @param offline Whether logged events should be sent to Amplitude servers. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)setOffline:(BOOL)offline; +- (Amplitude *)setOffline:(BOOL)offline; /** Enables location tracking. If the user has granted your app location permissions, the SDK will also grab the location of the user. Amplitude will never prompt the user for location permissions itself, this must be done by your app. + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + **Note:** the user's location is only fetched once per session. Use `updateLocation` to force the SDK to fetch the user's latest location. */ -- (void)enableLocationListening; +- (Amplitude *)enableLocationListening; /** Disables location tracking. If you want location tracking disabled on startup of the app, call disableLocationListening before you call initializeApiKey. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)disableLocationListening; +- (Amplitude *)disableLocationListening; /** Forces the SDK to update with the user's last known location if possible. @@ -434,16 +469,23 @@ Uses advertisingIdentifier instead of identifierForVendor as the device ID Apple prohibits the use of advertisingIdentifier if your app does not have advertising. Useful for tying together data from advertising campaigns to anlaytics data. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. - **NOTE:** Must be called before initializeApiKey: is called to function. + **NOTE:** Must be called before initialize is called. */ -- (void)useAdvertisingIdForDeviceId; +- (Amplitude *)useAdvertisingIdForDeviceId; /**----------------------------------------------------------------------------- * @name Other Methods * ----------------------------------------------------------------------------- */ +/** + Whether or to opt the current user out of tracking. If true then this blocks the logging of any events and properties, and blocks the sending of events to Amplitude servers. + */ +- (BOOL)optOut; + /** Prints the number of events in the queue. diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index ec0aa8fb..de2a079f 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -450,10 +450,10 @@ - (BOOL)checkApiKeyForMethod:(NSString*) methodName return YES; } -- (void)initialize +- (Amplitude *)initialize { if (![self checkApiKeyForMethod:@"initialize"]) { - return; + return self; } UIApplication *app = [self getSharedApplication]; @@ -465,6 +465,7 @@ - (void)initialize [self enterForeground]; } } + return self; } - (UIApplication *)getSharedApplication @@ -1197,14 +1198,14 @@ - (void)identify:(AMPIdentify *)identify #pragma mark - configurations -- (void)setUserProperties:(NSDictionary*) userProperties +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties { if (![self checkApiKeyForMethod:@"setUserProperties"]) { - return; + return self; } if (userProperties == nil || ![self isArgument:userProperties validType:[NSDictionary class] methodName:@"setUserProperties:"] || [userProperties count] == 0) { - return; + return self; } NSDictionary *copy = [userProperties copy]; @@ -1216,49 +1217,51 @@ - (void)setUserProperties:(NSDictionary*) userProperties } [self identify:identify]; }]; + return self; } // maintain for legacy // replace argument is deprecated. In earlier versions of this SDK, this replaced the in-memory userProperties dictionary with the input, but now userProperties are no longer stored in memory. -- (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace { - [self setUserProperties:userProperties]; + return [self setUserProperties:userProperties]; } -- (void)clearUserProperties +- (Amplitude *)clearUserProperties { if (![self checkApiKeyForMethod:@"clearUserProperties"]) { - return; + return self; } AMPIdentify *identify = [[AMPIdentify identify] clearAll]; [self identify:identify]; + return self; } -- (void)setGroup:(NSString *)groupType groupName:(NSObject *)groupName +- (Amplitude *)setGroup:(NSString *)groupType groupName:(NSObject *)groupName { if (![self checkApiKeyForMethod:@"setGroup"]) { - return; + return self; } if (groupType == nil || [groupType isEqualToString:@""]) { AMPLITUDE_LOG(@"ERROR: groupType cannot be nil or an empty string"); - return; + return self; } NSMutableDictionary *groups = [NSMutableDictionary dictionaryWithObjectsAndKeys:groupName, groupType, nil]; AMPIdentify *identify = [[AMPIdentify identify] set:groupType value:groupName]; [self logEvent:IDENTIFY_EVENT withEventProperties:nil withApiProperties:nil withUserProperties:identify.userPropertyOperations withGroups:groups withTimestamp:nil outOfSession:NO]; - + return self; } -- (void)setUserId:(NSString*) userId +- (Amplitude *)setUserId:(NSString*) userId { if (![self checkApiKeyForMethod:@"setUserId"]) { - return; + return self; } if (!(userId == nil || [self isArgument:userId validType:[NSString class] methodName:@"setUserId:"])) { - return; + return self; } [self runOnBackgroundQueue:^{ @@ -1267,27 +1270,30 @@ - (void)setUserId:(NSString*) userId _userId = userId; (void) [self.dbHelper insertOrReplaceKeyValue:USER_ID value:_userId]; }]; + return self; } -- (void)setOptOut:(BOOL)enabled +- (Amplitude *)setOptOut:(BOOL)enabled { if (![self checkApiKeyForMethod:@"setOptOut"]) { - return; + return self; } [self runOnBackgroundQueue:^{ NSNumber *value = [NSNumber numberWithBool:enabled]; (void) [self.dbHelper insertOrReplaceKeyLongValue:OPT_OUT value:value]; }]; + return self; } -- (void)setOffline:(BOOL)offline +- (Amplitude *)setOffline:(BOOL)offline { _offline = offline; if (!_offline && _apiKey != nil) { [self uploadEvents]; } + return self; } - (void)setEventUploadMaxBatchSize:(int) eventUploadMaxBatchSize @@ -1304,15 +1310,15 @@ - (BOOL)optOut return [[self.dbHelper getLongValue:OPT_OUT] boolValue]; } -- (void)setDeviceId:(NSString*)deviceId +- (Amplitude *)setDeviceId:(NSString*)deviceId { if (![self checkApiKeyForMethod:@"setDeviceId"]) { - return; + return self; } if (![self isValidDeviceId:deviceId]) { AMPLITUDE_ERROR(@"ERROR: invalid deviceId '%@', skipping setDeviceId", deviceId); - return; + return self; } [self runOnBackgroundQueue:^{ @@ -1321,6 +1327,7 @@ - (void)setDeviceId:(NSString*)deviceId _deviceId = deviceId; (void) [self.dbHelper insertOrReplaceKeyValue:DEVICE_ID value:deviceId]; }]; + return self; } #pragma mark - location methods @@ -1339,20 +1346,23 @@ - (void)updateLocation } } -- (void)enableLocationListening +- (Amplitude *)enableLocationListening { _locationListeningEnabled = YES; [self updateLocation]; + return self; } -- (void)disableLocationListening +- (Amplitude *)disableLocationListening { _locationListeningEnabled = NO; + return self; } -- (void)useAdvertisingIdForDeviceId +- (Amplitude *)useAdvertisingIdForDeviceId { _useAdvertisingIdForDeviceId = YES; + return self; } #pragma mark - Getters for device data diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 71ba065f..b3c32ba4 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -110,8 +110,7 @@ - (void)testSeparateInstancesLogEventsSeparate { [newDBHelper2 resetDB:NO]; // setup existing database file, init default instance - [[[Amplitude instance] setApiKey:apiKey] initialize]; - [[Amplitude instance] flushQueue]; + [[[[Amplitude instance] setApiKey:apiKey] initialize] flushQueue]; [oldDbHelper insertOrReplaceKeyLongValue:@"sequence_number" value:[NSNumber numberWithLongLong:1000]]; [oldDbHelper addEvent:@"{\"event_type\":\"oldEvent\"}"]; [oldDbHelper addIdentify:@"{\"event_type\":\"$identify\"}"]; @@ -130,8 +129,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertNil([newDBHelper2 getLongValue:@"sequence_number"]); // init first new app and verify separate database - [[[Amplitude instanceWithName:newInstance1] setApiKey:newApiKey1] initialize]; - [[Amplitude instanceWithName:newInstance1] flushQueue]; + [[[[Amplitude instanceWithName:newInstance1] setApiKey:newApiKey1] initialize] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], [newDBHelper1 getValue:@"device_id"]); XCTAssertEqual([[Amplitude instanceWithName:newInstance1] getNextSequenceNumber], 1); @@ -139,8 +137,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertEqual([newDBHelper1 getIdentifyCount], 0); // init second new app and verify separate database - [[[Amplitude instanceWithName:newInstance2] setApiKey:newApiKey2] initialize]; - [[Amplitude instanceWithName:newInstance2] flushQueue]; + [[[[Amplitude instanceWithName:newInstance2] setApiKey:newApiKey2] initialize] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], [newDBHelper2 getValue:@"device_id"]); XCTAssertEqual([[Amplitude instanceWithName:newInstance2] getNextSequenceNumber], 1); @@ -188,8 +185,7 @@ - (void)testInitializeLoadUserIdFromEventData { NSString *testUserId = @"testUserId"; AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; [dbHelper insertOrReplaceKeyValue:@"user_id" value:testUserId]; - [[client setApiKey:apiKey] initialize]; - [client flushQueue]; + [[[client setApiKey:apiKey] initialize] flushQueue]; XCTAssertTrue([[client userId] isEqualToString:testUserId]); } @@ -198,9 +194,7 @@ - (void)testInitializeWithNilUserId { XCTAssertEqual([self.amplitude userId], nil); NSString *nilUserId = nil; - [[self.amplitude setApiKey:apiKey] setUserId:nilUserId]; - [self.amplitude initialize]; - [self.amplitude flushQueue]; + [[[[self.amplitude setApiKey:apiKey] setUserId:nilUserId] initialize] flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); XCTAssertNil([self.databaseHelper getValue:@"user_id"]); } @@ -212,9 +206,7 @@ - (void)testInitializeWithUserId { XCTAssertEqual([client userId], nil); NSString *testUserId = @"testUserId"; - [[client setApiKey:apiKey] setUserId:testUserId]; - [client initialize]; - [client flushQueue]; + [[[[client setApiKey:apiKey] setUserId:testUserId] initialize] flushQueue]; XCTAssertEqual([client userId], testUserId); } @@ -223,9 +215,7 @@ - (void)testSkipReinitialization { XCTAssertEqual([self.amplitude userId], nil); NSString *testUserId = @"testUserId"; - [[self.amplitude setApiKey:apiKey] setUserId:testUserId]; - [self.amplitude initialize]; - [self.amplitude flushQueue]; + [[[[self.amplitude setApiKey:apiKey] setUserId:testUserId] initialize] flushQueue]; // since setUserId is called explicitly now, will override existing value even though reinitialization skipped XCTAssertEqualObjects([self.amplitude userId], testUserId); } @@ -241,8 +231,7 @@ - (void)testDatabaseScopeMigration { // init new client, verify migration Amplitude *client = [Amplitude instanceWithName:migrationInstanceName]; - [[client setApiKey:migrationApiKey] initialize]; - [client flushQueue]; + [[[client setApiKey:migrationApiKey] initialize] flushQueue]; XCTAssertEqualObjects([client getDeviceId], deviceId); AMPDatabaseHelper *newDbHelper = [AMPDatabaseHelper getDatabaseHelper:migrationInstanceName apiKey:migrationApiKey]; @@ -254,8 +243,7 @@ - (void)testClearUserId { XCTAssertEqual([self.amplitude userId], nil); NSString *testUserId = @"testUserId"; - [self.amplitude setUserId:testUserId]; - [self.amplitude flushQueue]; + [[self.amplitude setUserId:testUserId] flushQueue]; XCTAssertEqual([self.amplitude userId], testUserId); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; @@ -263,8 +251,7 @@ - (void)testClearUserId { XCTAssert([[event1 objectForKey:@"user_id"] isEqualToString:testUserId]); NSString *nilUserId = nil; - [self.amplitude setUserId:nilUserId]; - [self.amplitude flushQueue]; + [[self.amplitude setUserId:nilUserId] flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; diff --git a/AmplitudeTests/BaseTestCase.m b/AmplitudeTests/BaseTestCase.m index 92f2c485..d48eba08 100644 --- a/AmplitudeTests/BaseTestCase.m +++ b/AmplitudeTests/BaseTestCase.m @@ -29,8 +29,7 @@ - (void)setUp { self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:apiKey]; XCTAssertTrue([self.databaseHelper resetDB:NO]); - [self.amplitude init]; - [self.amplitude flushQueue]; + [[self.amplitude init] flushQueue]; self.amplitude.sslPinningEnabled = NO; } diff --git a/AmplitudeTests/SessionTests.m b/AmplitudeTests/SessionTests.m index 0c43cd8b..12381719 100644 --- a/AmplitudeTests/SessionTests.m +++ b/AmplitudeTests/SessionTests.m @@ -41,8 +41,7 @@ - (void)testSessionAutoStartedBackground { // mock amplitude object and verify enterForeground not called id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude reject] enterForeground]; - [[mockAmplitude setApiKey:apiKey] initialize]; - [mockAmplitude flushQueue]; + [[[mockAmplitude setApiKey:apiKey] initialize] flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); } @@ -54,8 +53,7 @@ - (void)testSessionAutoStartedInactive { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude expect] enterForeground]; - [[mockAmplitude setApiKey:apiKey] initialize]; - [mockAmplitude flushQueue]; + [[[mockAmplitude setApiKey:apiKey] initialize] flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); } @@ -67,9 +65,7 @@ - (void)testSessionHandling { NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; - [[mockAmplitude setApiKey:apiKey] setUserId:nil]; - [mockAmplitude initialize]; - [mockAmplitude flushQueue]; + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); XCTAssertEqual([mockAmplitude sessionId], 1000000); @@ -137,10 +133,7 @@ - (void)testSessionHandling { } - (void)testEnterBackgroundDoesNotTrackEvent { - [[self.amplitude setApiKey:apiKey] setUserId:nil]; - [self.amplitude initialize]; - [self.amplitude flushQueue]; - + [[[[self.amplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil userInfo:nil]; @@ -154,10 +147,7 @@ - (void)testTrackSessionEvents { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [[mockAmplitude setApiKey:apiKey] setUserId:nil]; - [mockAmplitude initialize]; - [mockAmplitude flushQueue]; - + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); XCTAssertEqual([[mockAmplitude getLastEvent][@"session_id"] longLongValue], 1000000); XCTAssertEqualObjects([mockAmplitude getLastEvent][@"event_type"], kAMPSessionStartEvent); @@ -190,10 +180,7 @@ - (void)testSessionEventsOn32BitDevices { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [[mockAmplitude setApiKey:apiKey] setUserId:nil]; - [mockAmplitude initialize]; - [mockAmplitude flushQueue]; - + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); XCTAssertEqual([[mockAmplitude getLastEvent][@"session_id"] longLongValue], 21474836470000); XCTAssertEqualObjects([mockAmplitude getLastEvent][@"event_type"], kAMPSessionStartEvent); @@ -224,9 +211,7 @@ - (void)testSkipSessionCheckWhenLoggingSessionEvents { [self.databaseHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; self.amplitude.trackingSessionEvents = YES; - [[self.amplitude setApiKey:apiKey] setUserId:nil]; - [self.amplitude initialize]; - [self.amplitude flushQueue]; + [[[[self.amplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([self.databaseHelper getEventCount], 2); NSArray *events = [self.databaseHelper getEvents:-1 limit:2]; XCTAssertEqualObjects(events[0][@"event_type"], kAMPSessionEndEvent); From d82841985dbac5673de15d01ae206b7e57bc94b5 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Mon, 22 Aug 2016 14:57:16 -0700 Subject: [PATCH 6/7] cleanup --- Amplitude/AMPDatabaseHelper.h | 2 +- Amplitude/AMPDatabaseHelper.m | 10 +++++----- Amplitude/AMPUtils.h | 2 +- Amplitude/AMPUtils.m | 2 +- Amplitude/Amplitude.h | 8 ++++---- Amplitude/Amplitude.m | 11 +++++++---- AmplitudeTests/AMPDatabaseHelperTests.m | 2 +- AmplitudeTests/Amplitude+Test.h | 5 +++-- AmplitudeTests/Amplitude+Test.m | 10 +++++----- AmplitudeTests/AmplitudeTests.m | 2 +- AmplitudeTests/SetupTests.m | 2 +- CHANGELOG.md | 1 + 12 files changed, 31 insertions(+), 26 deletions(-) diff --git a/Amplitude/AMPDatabaseHelper.h b/Amplitude/AMPDatabaseHelper.h index 5c6dd781..f7bec033 100644 --- a/Amplitude/AMPDatabaseHelper.h +++ b/Amplitude/AMPDatabaseHelper.h @@ -11,7 +11,7 @@ @property (nonatomic, strong, readonly) NSString *databasePath; + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey; -+ (AMPDatabaseHelper*)getDatabaseHelperWithInstanceName:(NSString*) instanceName; // for testing only ++ (AMPDatabaseHelper*)getTestDatabaseHelper:(NSString*) instanceName; // for testing only - (BOOL)createTables; - (BOOL)dropTables; - (BOOL)upgrade:(int) oldVersion newVersion:(int) newVersion; diff --git a/Amplitude/AMPDatabaseHelper.m b/Amplitude/AMPDatabaseHelper.m index 87f92d45..66bb74ff 100644 --- a/Amplitude/AMPDatabaseHelper.m +++ b/Amplitude/AMPDatabaseHelper.m @@ -86,7 +86,7 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSStrin @synchronized(_instances) { dbHelper = [_instances objectForKey:instanceName]; if (dbHelper == nil) { - dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName andApiKey:apiKey]; + dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName apiKey:apiKey]; [_instances setObject:dbHelper forKey:instanceName]; SAFE_ARC_RELEASE(dbHelper); } @@ -94,16 +94,16 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSStrin return dbHelper; } -// for testing only -+ (AMPDatabaseHelper*)getDatabaseHelperWithInstanceName:(NSString*) instanceName +// for testing only, returns an instance with legacy filename ++ (AMPDatabaseHelper*)getTestDatabaseHelper:(NSString*) instanceName { - AMPDatabaseHelper *dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName andApiKey:nil]; + AMPDatabaseHelper *dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName apiKey:nil]; return SAFE_ARC_AUTORELEASE(dbHelper); } // instanceName should not be null, getDatabaseHelper will guard // apiKey should only be null for testing - Amplitude client will guard -- (id)initWithInstanceName:(NSString*) instanceName andApiKey:(NSString*) apiKey +- (id)initWithInstanceName:(NSString*) instanceName apiKey:(NSString*) apiKey { if ((self = [super init])) { NSString *databaseDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; diff --git a/Amplitude/AMPUtils.h b/Amplitude/AMPUtils.h index 7b8c7059..20c3e13b 100644 --- a/Amplitude/AMPUtils.h +++ b/Amplitude/AMPUtils.h @@ -12,6 +12,6 @@ + (id) makeJSONSerializable:(id) obj; + (BOOL) isEmptyString:(NSString*) str; + (NSDictionary*) validateGroups:(NSDictionary*) obj; -+ (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to; ++ (BOOL) moveFileIfNotExists:(NSString*)from to:(NSString*)to; @end diff --git a/Amplitude/AMPUtils.m b/Amplitude/AMPUtils.m index 821712e4..c68690ac 100644 --- a/Amplitude/AMPUtils.m +++ b/Amplitude/AMPUtils.m @@ -141,7 +141,7 @@ + (NSDictionary *) validateGroups:(NSDictionary *) obj return [NSDictionary dictionaryWithDictionary:dict]; } -+ (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to ++ (BOOL) moveFileIfNotExists:(NSString*)from to:(NSString*)to { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; diff --git a/Amplitude/Amplitude.h b/Amplitude/Amplitude.h index 441b20de..acbce9e5 100644 --- a/Amplitude/Amplitude.h +++ b/Amplitude/Amplitude.h @@ -22,7 +22,7 @@ [eventProperties setValue:@"VALUE_GOES_HERE" forKey:@"KEY_GOES_HERE"]; [[Amplitude instance] logEvent:@"Compute Hash" withEventProperties:eventProperties]; - 5. You can configure the SDK via configurable properties and public set methods such as `setUserId`. You can modify the instance properties at any time, for example `[Amplitude instance].trackingSessionEvents = YES;`. The public set methods should be called after setting the apiKey and before calling initialize. For example `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`, otherwise you can call them when appropriate, for example calling `setUserId` after the user logs in, etc. + 5. You can configure the SDK via configurable properties and helper methods. You can modify the instance properties at any time, for example `[Amplitude instance].trackingSessionEvents = YES;`. If you plan to call any helper methods to configure the SDK before the first event is logged (for example `setUserId`, or `enableLocationListening`, or `userAdvertisingIdForDeviceId`), you need to do so after calling setApiKey and before calling initialize. For example `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`, otherwise you can call them when appropriate, for example calling `setUserId` after the user logs in, etc. **Note:** you should call SDK methods on an Amplitude instance, for example logging events with the default instance: `[[Amplitude instance] logEvent:@"testEvent"];` @@ -132,7 +132,7 @@ We recommend you set the api key in your app's "didFinishLaunchingWithOptions" method inside your app delegate. - **Note:** this is required before you can log any events. + **Note:** this is required before you can log any events as well as configure the SDK with any of the helper methods. @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings @@ -148,7 +148,7 @@ /** After you set the API key call this to enable event tracking. - **Note:** If you are configuring the SDK before tracking (either by modifying the configurable properties or calling any public set methods), do so before calling initialize. For example: `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`. + **Note:** If you are configuring the SDK before tracking the first event, do so before calling initialize. For example: `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`. **Note:** this is required before you can log any events. @@ -472,7 +472,7 @@ @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. - **NOTE:** Must be called before initialize is called. + **NOTE:** If the current device already has a deviceId, calling useAdvertisingIdForDeviceId will override it with the advertisingIdentifier. */ - (Amplitude *)useAdvertisingIdForDeviceId; diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index de2a079f..e8804162 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -50,7 +50,6 @@ @interface Amplitude () @property (nonatomic, strong) NSOperationQueue *backgroundQueue; @property (nonatomic, strong) AMPDatabaseHelper *dbHelper; -@property (nonatomic, assign) BOOL initializedDatabase; @property (nonatomic, assign) BOOL sslPinningEnabled; @property (nonatomic, assign) long long sessionId; @@ -208,7 +207,6 @@ - (id)initWithInstanceName:(NSString*) instanceName _sslPinningEnabled = NO; #endif - _initializedDatabase = NO; _locationListeningEnabled = YES; _sessionId = -1; _updateScheduled = NO; @@ -392,7 +390,7 @@ - (Amplitude *)setApiKey:(NSString*) apiKey return self; } - if (!_initializedDatabase) { + if (!_apiKey) { SAFE_ARC_RETAIN(apiKey); SAFE_ARC_RELEASE(_apiKey); _apiKey = apiKey; @@ -435,7 +433,6 @@ - (Amplitude *)setApiKey:(NSString*) apiKey // now we can add observers after setting apikey since enterBackground saves timestamp to DB [self addObservers]; - _initializedDatabase = YES; } return self; @@ -1362,6 +1359,12 @@ - (Amplitude *)disableLocationListening - (Amplitude *)useAdvertisingIdForDeviceId { _useAdvertisingIdForDeviceId = YES; + + // if called after setApiKey, then deviceId will already be initialized, need to overwrite with IDFA + if (_apiKey && ![AMPUtils isEmptyString:_deviceInfo.advertiserID]) { + (void) [self setDeviceId:_deviceInfo.advertiserID]; + } + return self; } diff --git a/AmplitudeTests/AMPDatabaseHelperTests.m b/AmplitudeTests/AMPDatabaseHelperTests.m index 1e21245e..1c0f2550 100644 --- a/AmplitudeTests/AMPDatabaseHelperTests.m +++ b/AmplitudeTests/AMPDatabaseHelperTests.m @@ -41,7 +41,7 @@ - (void)testScopeMigration { NSString *apiKey = @"migrationApiKey"; // initialize dbHelper with old filename - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelperWithInstanceName:instanceName]; + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getTestDatabaseHelper:instanceName]; [dbHelper insertOrReplaceKeyValue:@"migrationTestKey" value:@"migrationTestValue"]; // force migration diff --git a/AmplitudeTests/Amplitude+Test.h b/AmplitudeTests/Amplitude+Test.h index 1be25d52..019f6f23 100644 --- a/AmplitudeTests/Amplitude+Test.h +++ b/AmplitudeTests/Amplitude+Test.h @@ -7,15 +7,16 @@ // #import +#import "AMPDatabaseHelper.h" @interface Amplitude (Test) @property (nonatomic, strong) NSOperationQueue *backgroundQueue; @property (nonatomic, strong) NSOperationQueue *initializerQueue; @property (nonatomic, strong) NSMutableDictionary *eventsData; -@property (nonatomic, assign) BOOL initializedDatabase; @property (nonatomic, assign) long long sessionId; -@property (nonatomic, strong) NSNumber* lastEventTime; +@property (nonatomic, strong) NSNumber *lastEventTime; +@property (nonatomic, strong) AMPDatabaseHelper *dbHelper; - (void)flushQueue; - (void)flushQueueWithQueue:(NSOperationQueue*) queue; diff --git a/AmplitudeTests/Amplitude+Test.m b/AmplitudeTests/Amplitude+Test.m index a5c51cba..fe46a887 100644 --- a/AmplitudeTests/Amplitude+Test.m +++ b/AmplitudeTests/Amplitude+Test.m @@ -15,9 +15,9 @@ @implementation Amplitude (Test) @dynamic backgroundQueue; @dynamic initializerQueue; @dynamic eventsData; -@dynamic initializedDatabase; @dynamic sessionId; @dynamic lastEventTime; +@dynamic dbHelper; NSString *const newTestApiKey = @"000000"; @@ -30,22 +30,22 @@ - (void)flushQueueWithQueue:(NSOperationQueue*) queue { } - (NSDictionary *)getEvent:(NSInteger) fromEnd { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEvents:-1 limit:-1]; + NSArray *events = [self.dbHelper getEvents:-1 limit:-1]; return [events objectAtIndex:[events count] - fromEnd - 1]; } - (NSDictionary *)getLastEvent { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEvents:-1 limit:-1]; + NSArray *events = [self.dbHelper getEvents:-1 limit:-1]; return [events lastObject]; } - (NSDictionary *)getLastIdentify { - NSArray *identifys = [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getIdentifys:-1 limit:-1]; + NSArray *identifys = [self.dbHelper getIdentifys:-1 limit:-1]; return [identifys lastObject]; } - (NSUInteger)queuedEventCount { - return [[AMPDatabaseHelper getDatabaseHelper:nil apiKey:newTestApiKey] getEventCount]; + return [self.dbHelper getEventCount]; } - (void)flushUploads:(void (^)())handler { diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index b3c32ba4..9fa7a304 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -226,7 +226,7 @@ - (void)testDatabaseScopeMigration { NSString *deviceId = @"testMigrationDeviceId"; // create old database file - AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getDatabaseHelperWithInstanceName:migrationInstanceName]; + AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getTestDatabaseHelper:migrationInstanceName]; [oldDbHelper insertOrReplaceKeyValue:@"device_id" value:deviceId]; // init new client, verify migration diff --git a/AmplitudeTests/SetupTests.m b/AmplitudeTests/SetupTests.m index 5cf5fc8e..0e15f9bc 100644 --- a/AmplitudeTests/SetupTests.m +++ b/AmplitudeTests/SetupTests.m @@ -58,7 +58,7 @@ - (void)testUserIdSet { - (void)testInitializedSet { [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; - XCTAssert([self.amplitude initializedDatabase]); + XCTAssertEqual(self.amplitude.apiKey, apiKey); } - (void)testOptOut { diff --git a/CHANGELOG.md b/CHANGELOG.md index 8daa31da..11bb754d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Migrate Sqlite database file to new filename with apiKey. * Migrate all database logic from `init` to `initializeApiKey`. * Run `init` logic on `backgroundQueue`, removing need for separate `initializerQueue`. +* Many methods (such as `setApiKey`, `setUserId`, `setUserProperties`, etc) now return the Amplitude instance, allowing you to chain multiple method calls together. ### 3.8.4 Re-release (August 19, 2016) From d54ac4245d795b4ccd39e30c9c96116a6716ec49 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Mon, 22 Aug 2016 15:21:26 -0700 Subject: [PATCH 7/7] update readme and changelog --- CHANGELOG.md | 3 ++- README.md | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11bb754d..ffa03ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Unreleased +* This is an API-breaking change that replaces `initializeApiKey` with two new methods `setApiKey` and `initialize`. See [Readme](https://github.com/amplitude/Amplitude-iOS#400-update-and-api-breaking-changes-to-SDK-initialization) for details. * Migrate Sqlite database file to new filename with apiKey. -* Migrate all database logic from `init` to `initializeApiKey`. +* Migrate all database logic from `init` to `setApiKey`. * Run `init` logic on `backgroundQueue`, removing need for separate `initializerQueue`. * Many methods (such as `setApiKey`, `setUserId`, `setUserProperties`, etc) now return the Amplitude instance, allowing you to chain multiple method calls together. diff --git a/README.md b/README.md index b1fe8397..c2e63656 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ See our [SDK documentation](https://rawgit.com/amplitude/Amplitude-iOS/master/do #import "Amplitude.h" ``` -6. In the application:didFinishLaunchingWithOptions: method of your YourAppNameAppDelegate.m file, initialize the SDK: +6. In the application:didFinishLaunchingWithOptions: method of your YourAppNameAppDelegate.m file, set the API key you received from Step 1. Then call `initialize` to initialize the event tracking: ``` objective-c - [[Amplitude instance] initializeApiKey:@"YOUR_API_KEY_HERE"]; + [[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] initialize]; ``` 7. To track an event anywhere in the app, call: @@ -41,6 +41,25 @@ See our [SDK documentation](https://rawgit.com/amplitude/Amplitude-iOS/master/do 8. Events are saved locally. Uploads are batched to occur every 30 events and every 30 seconds, as well as on app close. After calling logEvent in your app, you will immediately see data appear on the Amplitude Website. +# 4.0.0 Update and API-breaking changes to SDK initialization # + +Version 4.0.0 is a major update that simplifies how you configure the SDK during initialization. Before v4.0.0 you would initialize the SDK with your API key by calling `initializeApiKey:@"YOUR_API_KEY"`. In v4.0.0 that method has been removed and replace with two new methods `setApiKey:@"YOUR_API_KEY"` to set your API key and `initialize` to initialize the event tracking. + +**NOTE** Since `initialize` starts the event tracking logic, any SDK configuration that you want to do before the first event is logged needs to be done AFTER calling `setApiKey` and BEFORE calling `initialize`. This includes modifying any of the SDK's configurable properties and calling any of the helper methods such as `setUserId`, `setUserProperties`, `useAdvertisingIdForDeviceId`, `enableLocationListening`, etc. + +The helper methods now return the Amplitude instance, allowing you to easily chain multiple method calls together. Example: + +``` objective-c +[[[[[[Amplitude instance] setApiKey:@"API_KEY"] setUserId:userId] useAdvertisingIdForDeviceId] enableLocationListening] initialize]; +``` + +If you track session events you might do something like this: +``` objective-c +[[Amplitude instance] setApiKey:@"API_KEY"]; +[Amplitude instance].trackingSessionEvents = YES; +[[[Amplitude instance] setUserId:userId] initialize]; +``` + # Tracking Events # It's important to think about what types of events you care about as a developer. You should aim to track between 20 and 200 types of events on your site. Common event types are actions the user initiates (such as pressing a button) and events you want the user to complete (such as filling out a form, completing a level, or making a payment).