GDTCORUploadCoordinator.m 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
  17. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORAssert.h"
  18. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
  19. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
  20. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORReachability.h"
  21. #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
  22. @implementation GDTCORUploadCoordinator
  23. + (instancetype)sharedInstance {
  24. static GDTCORUploadCoordinator *sharedUploader;
  25. static dispatch_once_t onceToken;
  26. dispatch_once(&onceToken, ^{
  27. sharedUploader = [[GDTCORUploadCoordinator alloc] init];
  28. [sharedUploader startTimer];
  29. });
  30. return sharedUploader;
  31. }
  32. - (instancetype)init {
  33. self = [super init];
  34. if (self) {
  35. _coordinationQueue =
  36. dispatch_queue_create("com.google.GDTCORUploadCoordinator", DISPATCH_QUEUE_SERIAL);
  37. _registrar = [GDTCORRegistrar sharedInstance];
  38. _timerInterval = 30 * NSEC_PER_SEC;
  39. _timerLeeway = 5 * NSEC_PER_SEC;
  40. }
  41. return self;
  42. }
  43. - (void)forceUploadForTarget:(GDTCORTarget)target {
  44. dispatch_async(_coordinationQueue, ^{
  45. GDTCORLogDebug(@"Forcing an upload of target %ld", (long)target);
  46. GDTCORUploadConditions conditions = [self uploadConditions];
  47. conditions |= GDTCORUploadConditionHighPriority;
  48. [self uploadTargets:@[ @(target) ] conditions:conditions];
  49. });
  50. }
  51. #pragma mark - Private helper methods
  52. /** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will
  53. * check the next-upload clocks of all targets to determine if an upload attempt can be made.
  54. */
  55. - (void)startTimer {
  56. dispatch_async(_coordinationQueue, ^{
  57. if (self->_timer) {
  58. // The timer has been already started.
  59. return;
  60. }
  61. self->_timer =
  62. dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue);
  63. dispatch_source_set_timer(self->_timer, DISPATCH_TIME_NOW, self->_timerInterval,
  64. self->_timerLeeway);
  65. dispatch_source_set_event_handler(self->_timer, ^{
  66. if (![[GDTCORApplication sharedApplication] isRunningInBackground]) {
  67. GDTCORUploadConditions conditions = [self uploadConditions];
  68. GDTCORLogDebug(@"%@", @"Upload timer fired");
  69. [self uploadTargets:[self.registrar.targetToUploader allKeys] conditions:conditions];
  70. }
  71. });
  72. GDTCORLogDebug(@"%@", @"Upload timer started");
  73. dispatch_resume(self->_timer);
  74. });
  75. }
  76. /** Stops the currently running timer. */
  77. - (void)stopTimer {
  78. if (_timer) {
  79. dispatch_source_cancel(_timer);
  80. _timer = nil;
  81. }
  82. }
  83. /** Triggers the uploader implementations for the given targets to upload.
  84. *
  85. * @param targets An array of targets to trigger.
  86. * @param conditions The set of upload conditions.
  87. */
  88. - (void)uploadTargets:(NSArray<NSNumber *> *)targets conditions:(GDTCORUploadConditions)conditions {
  89. dispatch_async(_coordinationQueue, ^{
  90. // TODO: The reachability signal may be not reliable enough to prevent an upload attempt.
  91. // See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details.
  92. if ((conditions & GDTCORUploadConditionNoNetwork) == GDTCORUploadConditionNoNetwork) {
  93. return;
  94. }
  95. for (NSNumber *target in targets) {
  96. id<GDTCORUploader> uploader = self->_registrar.targetToUploader[target];
  97. [uploader uploadTarget:target.intValue withConditions:conditions];
  98. }
  99. });
  100. }
  101. - (void)signalToStoragesToCheckExpirations {
  102. for (id<GDTCORStorageProtocol> storage in [_registrar.targetToStorage allValues]) {
  103. [storage checkForExpirations];
  104. }
  105. }
  106. /** Returns the registered storage for the given NSNumber wrapped GDTCORTarget.
  107. *
  108. * @param target The NSNumber wrapping of a GDTCORTarget to find the storage instance of.
  109. * @return The storage instance for the given target.
  110. */
  111. - (nullable id<GDTCORStorageProtocol>)storageForTarget:(NSNumber *)target {
  112. id<GDTCORStorageProtocol> storage = [GDTCORRegistrar sharedInstance].targetToStorage[target];
  113. GDTCORAssert(storage, @"A storage must be registered for target %@", target);
  114. return storage;
  115. }
  116. /** Returns the current upload conditions after making determinations about the network connection.
  117. *
  118. * @return The current upload conditions.
  119. */
  120. - (GDTCORUploadConditions)uploadConditions {
  121. GDTCORNetworkReachabilityFlags currentFlags = [GDTCORReachability currentFlags];
  122. BOOL networkConnected = GDTCORReachabilityFlagsReachable(currentFlags);
  123. if (!networkConnected) {
  124. return GDTCORUploadConditionNoNetwork;
  125. }
  126. BOOL isWWAN = GDTCORReachabilityFlagsContainWWAN(currentFlags);
  127. if (isWWAN) {
  128. return GDTCORUploadConditionMobileData;
  129. } else {
  130. return GDTCORUploadConditionWifiData;
  131. }
  132. }
  133. #pragma mark - GDTCORLifecycleProtocol
  134. - (void)appWillForeground:(GDTCORApplication *)app {
  135. // -startTimer is thread-safe.
  136. [self startTimer];
  137. [self signalToStoragesToCheckExpirations];
  138. }
  139. - (void)appWillBackground:(GDTCORApplication *)app {
  140. dispatch_async(_coordinationQueue, ^{
  141. [self stopTimer];
  142. });
  143. }
  144. - (void)appWillTerminate:(GDTCORApplication *)application {
  145. dispatch_sync(_coordinationQueue, ^{
  146. [self stopTimer];
  147. });
  148. }
  149. @end