AutorÃa | Ultima modificación | Ver Log |
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
@implementation GDTCORUploadCoordinator
+ (instancetype)sharedInstance {
static GDTCORUploadCoordinator *sharedUploader;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedUploader = [[GDTCORUploadCoordinator alloc] init];
[sharedUploader startTimer];
});
return sharedUploader;
}
- (instancetype)init {
self = [super init];
if (self) {
_coordinationQueue =
dispatch_queue_create("com.google.GDTCORUploadCoordinator", DISPATCH_QUEUE_SERIAL);
_registrar = [GDTCORRegistrar sharedInstance];
_timerInterval = 30 * NSEC_PER_SEC;
_timerLeeway = 5 * NSEC_PER_SEC;
}
return self;
}
- (void)forceUploadForTarget:(GDTCORTarget)target {
dispatch_async(_coordinationQueue, ^{
GDTCORLogDebug(@"Forcing an upload of target %ld", (long)target);
GDTCORUploadConditions conditions = [self uploadConditions];
conditions |= GDTCORUploadConditionHighPriority;
[self uploadTargets:@[ @(target) ] conditions:conditions];
});
}
#pragma mark - Private helper methods
/** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will
* check the next-upload clocks of all targets to determine if an upload attempt can be made.
*/
- (void)startTimer {
dispatch_async(_coordinationQueue, ^{
if (self->_timer) {
// The timer has been already started.
return;
}
// Delay the timer slightly so it doesn't run while +load calls are still running.
dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC / 2);
self->_timer =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue);
dispatch_source_set_timer(self->_timer, deadline, self->_timerInterval, self->_timerLeeway);
dispatch_source_set_event_handler(self->_timer, ^{
if (![[GDTCORApplication sharedApplication] isRunningInBackground]) {
GDTCORUploadConditions conditions = [self uploadConditions];
GDTCORLogDebug(@"%@", @"Upload timer fired");
[self uploadTargets:[self.registrar.targetToUploader allKeys] conditions:conditions];
}
});
GDTCORLogDebug(@"%@", @"Upload timer started");
dispatch_resume(self->_timer);
});
}
/** Stops the currently running timer. */
- (void)stopTimer {
if (_timer) {
dispatch_source_cancel(_timer);
_timer = nil;
}
}
/** Triggers the uploader implementations for the given targets to upload.
*
* @param targets An array of targets to trigger.
* @param conditions The set of upload conditions.
*/
- (void)uploadTargets:(NSArray<NSNumber *> *)targets conditions:(GDTCORUploadConditions)conditions {
dispatch_async(_coordinationQueue, ^{
// TODO: The reachability signal may be not reliable enough to prevent an upload attempt.
// See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details.
if ((conditions & GDTCORUploadConditionNoNetwork) == GDTCORUploadConditionNoNetwork) {
return;
}
for (NSNumber *target in targets) {
id<GDTCORUploader> uploader = self->_registrar.targetToUploader[target];
[uploader uploadTarget:target.intValue withConditions:conditions];
}
});
}
- (void)signalToStoragesToCheckExpirations {
// The same storage may be associated with several targets. Make sure to check for expirations
// only once per storage.
NSSet<id<GDTCORStorageProtocol>> *storages =
[NSSet setWithArray:[_registrar.targetToStorage allValues]];
for (id<GDTCORStorageProtocol> storage in storages) {
[storage checkForExpirations];
}
}
/** Returns the registered storage for the given NSNumber wrapped GDTCORTarget.
*
* @param target The NSNumber wrapping of a GDTCORTarget to find the storage instance of.
* @return The storage instance for the given target.
*/
- (nullable id<GDTCORStorageProtocol>)storageForTarget:(NSNumber *)target {
id<GDTCORStorageProtocol> storage = [GDTCORRegistrar sharedInstance].targetToStorage[target];
GDTCORAssert(storage, @"A storage must be registered for target %@", target);
return storage;
}
/** Returns the current upload conditions after making determinations about the network connection.
*
* @return The current upload conditions.
*/
- (GDTCORUploadConditions)uploadConditions {
GDTCORNetworkReachabilityFlags currentFlags = [GDTCORReachability currentFlags];
BOOL networkConnected = GDTCORReachabilityFlagsReachable(currentFlags);
if (!networkConnected) {
return GDTCORUploadConditionNoNetwork;
}
BOOL isWWAN = GDTCORReachabilityFlagsContainWWAN(currentFlags);
if (isWWAN) {
return GDTCORUploadConditionMobileData;
} else {
return GDTCORUploadConditionWifiData;
}
}
#pragma mark - GDTCORLifecycleProtocol
- (void)appWillForeground:(GDTCORApplication *)app {
// -startTimer is thread-safe.
[self startTimer];
[self signalToStoragesToCheckExpirations];
}
- (void)appWillBackground:(GDTCORApplication *)app {
dispatch_async(_coordinationQueue, ^{
[self stopTimer];
});
}
- (void)appWillTerminate:(GDTCORApplication *)application {
dispatch_sync(_coordinationQueue, ^{
[self stopTimer];
});
}
@end