Skip to content

Implement app-lifecycle groundwork #2800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions GoogleDataTransport/GoogleDataTransport/Classes/GDTLifecycle.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "GDTLifecycle.h"

#import <GoogleDataTransport/GDTEvent.h>

#import "GDTRegistrar_Private.h"
#import "GDTStorage_Private.h"
#import "GDTTransformer_Private.h"
#import "GDTUploadCoordinator_Private.h"

@implementation GDTLifecycle

+ (void)load {
[self sharedInstance];
}

/** Creates/returns the singleton instance of this class.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance {
static GDTLifecycle *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[GDTLifecycle alloc] init];
});
return sharedInstance;
}

- (instancetype)init {
self = [super init];
if (self) {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];

NSString *name = UIApplicationWillTerminateNotification;
[notificationCenter addObserver:self
selector:@selector(applicationWillTerminate:)
name:name
object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)applicationDidEnterBackground:(NSNotification *)notification {
// UIApplication *application = [UIApplication sharedApplication];
// [[GDTTransformer sharedInstance] appWillBackground:application];
// [[GDTStorage sharedInstance] appWillBackground:application];
// [[GDTUploadCoordinator sharedInstance] appWillBackground:application];
// [[GDTRegistrar sharedInstance] appWillBackground:application];
}

- (void)applicationWillEnterForeground:(NSNotification *)notification {
// UIApplication *application = [UIApplication sharedApplication];
// [[GDTTransformer sharedInstance] appWillForeground:application];
// [[GDTStorage sharedInstance] appWillForeground:application];
// [[GDTUploadCoordinator sharedInstance] appWillForeground:application];
// [[GDTRegistrar sharedInstance] appWillForeground:application];
}

- (void)applicationWillTerminate:(NSNotification *)notification {
// UIApplication *application = [UIApplication sharedApplication];
// [[GDTTransformer sharedInstance] appWillTerminate:application];
// [[GDTStorage sharedInstance] appWillTerminate:application];
// [[GDTUploadCoordinator sharedInstance] appWillTerminate:application];
// [[GDTRegistrar sharedInstance] appWillTerminate:application];
}

@end
9 changes: 9 additions & 0 deletions GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@

@implementation GDTStorage

+ (NSString *)archivePath {
static NSString *archivePath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
archivePath = [GDTStoragePath() stringByAppendingPathComponent:@"GDTStorageArchive"];
});
return archivePath;
}

+ (instancetype)sharedInstance {
static GDTStorage *sharedStorage;
static dispatch_once_t onceToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ + (instancetype)sharedInstance {
return sharedUploader;
}

+ (NSString *)archivePath {
static NSString *archivePath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *cachePath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSString *storagePath = [NSString stringWithFormat:@"%@/google-sdks-events", cachePath];
archivePath = [storagePath stringByAppendingPathComponent:@"GDTUploadCoordinator"];
});
return archivePath;
}

- (instancetype)init {
self = [super init];
if (self) {
Expand Down Expand Up @@ -149,6 +161,13 @@ - (void)startTimer {
});
}

/** Stops the currently running timer. */
- (void)stopTimer {
if (_timer) {
dispatch_source_cancel(_timer);
}
}

/** Checks the next upload time for each target and makes a determination on whether to upload
* events for that target or not. If so, queries the prioritizers
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ NS_ASSUME_NONNULL_BEGIN
/** The upload coordinator instance to use. */
@property(nonatomic) GDTUploadCoordinator *uploader;

/** If YES, every call to -storeLog results in background task and serializes the singleton to disk.
*/
@property(nonatomic, readonly) BOOL runningInBackground;

/** Returns the path to the keyed archive of the singleton. This is where the singleton is saved
* to disk during certain app lifecycle events.
*
* @return File path to serialized singleton.
*/
+ (NSString *)archivePath;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN
/** The storage instance used to store events. Should only be used to inject a testing fake. */
@property(nonatomic) GDTStorage *storageInstance;

/** If YES, every call to -transformEvent will result in a background task. */
@property(nonatomic, readonly) BOOL runningInBackground;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,22 @@ NS_ASSUME_NONNULL_BEGIN
/** The registrar object the coordinator will use. Generally used for testing. */
@property(nonatomic) GDTRegistrar *registrar;

/** If YES, completion and other operations will result in serializing the singleton to disk. */
@property(nonatomic, readonly) BOOL runningInBackground;

/** Returns the path to the keyed archive of the singleton. This is where the singleton is saved
* to disk during certain app lifecycle events.
*
* @return File path to serialized singleton.
*/
+ (NSString *)archivePath;

/** Starts the upload timer. */
- (void)startTimer;

/** Stops the upload timer from running. */
- (void)stopTimer;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class GDTEvent;

NS_ASSUME_NONNULL_BEGIN

/** A protocol defining the lifecycle events objects in the library must respond to immediately. */
@protocol GDTLifecycleProtocol <NSObject>

@required

/** Indicates an imminent app termination in the rare occurrence when -applicationWillTerminate: has
* been called.
*
* @param app The UIApplication instance.
*/
- (void)appWillTerminate:(UIApplication *)app;

/** Indicates that the app is moving to background and eventual suspension.
*
* @param app The UIApplication instance.
*/
- (void)appWillBackground:(UIApplication *)app;

/** Indicates that the app is resuming operation.
*
* @param app The UIApplication instance.
*/
- (void)appWillForeground:(UIApplication *)app;

@end

/** This class manages the library's response to app lifecycle events.
*
* When backgrounding, the library doesn't stop processing events, it's just that several background
* tasks will end up being created for every event that's sent, and the stateful objects of the
* library (GDTStorage and GDTUploadCoordinator singletons) will deserialize themselves from and to
* disk before and after every operation, respectively.
*/
@interface GDTLifecycle : NSObject <UIApplicationDelegate>

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
*/
typedef NS_ENUM(NSInteger, GDTTarget) {

/** A target only used in testing. */
kGDTTargetTest = 999,

/** The CCT target. */
kGDTTargetCCT = 1000,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#import "GDTUploadCoordinator.h"
#import "GDTUploadCoordinator_Private.h"

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -23,9 +24,6 @@ NS_ASSUME_NONNULL_BEGIN
/** Resets the properties of the singleton, but does not reallocate a new singleton. */
- (void)reset;

/** Stops the upload timer from running. */
- (void)stopTimer;

/** The time interval, in nanoseconds, that the time should be called on. */
@property(nonatomic, readwrite) uint64_t timerInterval;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ - (void)reset {
self.registrar = [GDTRegistrar sharedInstance];
}

- (void)stopTimer {
dispatch_source_cancel(self.timer);
}

- (void)setTimerInterval:(uint64_t)timerInterval {
[self setValue:@(timerInterval) forKey:@"_timerInterval"];
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, timerInterval, self.timerLeeway);
Expand Down
20 changes: 19 additions & 1 deletion GoogleDataTransport/Tests/Unit/GDTStorageTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,25 @@ - (void)testNSSecureCoding {
XCTAssertNil([[GDTStorage sharedInstance].storedEvents lastObject]);
});

// TODO(mikehaney24): Ensure that the object created by alloc is discarded?
GDTStorage *unarchivedStorage = [NSKeyedUnarchiver unarchiveObjectWithData:storageData];
XCTAssertNotNil([unarchivedStorage.storedEvents lastObject]);
}

/** Tests encoding and decoding the storage singleton when calling -sharedInstance. */
- (void)testNSSecureCodingWithSharedInstance {
GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target];
event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]);
event = nil;
NSData *storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTStorage sharedInstance]];
dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{
XCTAssertNotNil([[GDTStorage sharedInstance].storedEvents lastObject]);
});
[[GDTStorage sharedInstance] removeEvents:[GDTStorage sharedInstance].storedEvents.set];
dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{
XCTAssertNil([[GDTStorage sharedInstance].storedEvents lastObject]);
});

GDTStorage *unarchivedStorage = [NSKeyedUnarchiver unarchiveObjectWithData:storageData];
XCTAssertNotNil([unarchivedStorage.storedEvents lastObject]);
}
Expand Down