FIRMessagingPendingTopicsList.m 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. * Copyright 2017 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 "FIRMessagingPendingTopicsList.h"
  17. #import "FIRMessaging_Private.h"
  18. #import "FIRMessagingLogger.h"
  19. #import "FIRMessagingPubSub.h"
  20. #import "FIRMessagingDefines.h"
  21. NSString *const kPendingTopicBatchActionKey = @"action";
  22. NSString *const kPendingTopicBatchTopicsKey = @"topics";
  23. NSString *const kPendingBatchesEncodingKey = @"batches";
  24. NSString *const kPendingTopicsTimestampEncodingKey = @"ts";
  25. #pragma mark - FIRMessagingTopicBatch
  26. @interface FIRMessagingTopicBatch ()
  27. @property(nonatomic, strong, nonnull) NSMutableDictionary
  28. <NSString *, NSMutableArray <FIRMessagingTopicOperationCompletion> *> *topicHandlers;
  29. @end
  30. @implementation FIRMessagingTopicBatch
  31. - (instancetype)initWithAction:(FIRMessagingTopicAction)action {
  32. if (self = [super init]) {
  33. _action = action;
  34. _topics = [NSMutableSet set];
  35. _topicHandlers = [NSMutableDictionary dictionary];
  36. }
  37. return self;
  38. }
  39. #pragma mark NSCoding
  40. - (void)encodeWithCoder:(NSCoder *)aCoder {
  41. [aCoder encodeInteger:self.action forKey:kPendingTopicBatchActionKey];
  42. [aCoder encodeObject:self.topics forKey:kPendingTopicBatchTopicsKey];
  43. }
  44. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  45. // Ensure that our integer -> enum casting is safe
  46. NSInteger actionRawValue = [aDecoder decodeIntegerForKey:kPendingTopicBatchActionKey];
  47. FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe;
  48. if (actionRawValue == FIRMessagingTopicActionUnsubscribe) {
  49. action = FIRMessagingTopicActionUnsubscribe;
  50. }
  51. if (self = [self initWithAction:action]) {
  52. NSSet *topics = [aDecoder decodeObjectForKey:kPendingTopicBatchTopicsKey];
  53. if ([topics isKindOfClass:[NSSet class]]) {
  54. _topics = [topics mutableCopy];
  55. }
  56. _topicHandlers = [NSMutableDictionary dictionary];
  57. }
  58. return self;
  59. }
  60. @end
  61. #pragma mark - FIRMessagingPendingTopicsList
  62. @interface FIRMessagingPendingTopicsList ()
  63. @property(nonatomic, readwrite, strong) NSDate *archiveDate;
  64. @property(nonatomic, strong) NSMutableArray <FIRMessagingTopicBatch *> *topicBatches;
  65. @property(nonatomic, strong) FIRMessagingTopicBatch *currentBatch;
  66. @property(nonatomic, strong) NSMutableSet <NSString *> *topicsInFlight;
  67. @end
  68. @implementation FIRMessagingPendingTopicsList
  69. - (instancetype)init {
  70. if (self = [super init]) {
  71. _topicBatches = [NSMutableArray array];
  72. _topicsInFlight = [NSMutableSet set];
  73. }
  74. return self;
  75. }
  76. + (void)pruneTopicBatches:(NSMutableArray <FIRMessagingTopicBatch *> *)topicBatches {
  77. // For now, just remove empty batches. In the future we can use this to make the subscriptions
  78. // more efficient, by actually pruning topic actions that cancel each other out, for example.
  79. for (NSInteger i = topicBatches.count-1; i >= 0; i--) {
  80. FIRMessagingTopicBatch *batch = topicBatches[i];
  81. if (batch.topics.count == 0) {
  82. [topicBatches removeObjectAtIndex:i];
  83. }
  84. }
  85. }
  86. #pragma mark NSCoding
  87. - (void)encodeWithCoder:(NSCoder *)aCoder {
  88. [aCoder encodeObject:[NSDate date] forKey:kPendingTopicsTimestampEncodingKey];
  89. [aCoder encodeObject:self.topicBatches forKey:kPendingBatchesEncodingKey];
  90. }
  91. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  92. if (self = [self init]) {
  93. _archiveDate = [aDecoder decodeObjectForKey:kPendingTopicsTimestampEncodingKey];
  94. NSArray *archivedBatches = [aDecoder decodeObjectForKey:kPendingBatchesEncodingKey];
  95. if (archivedBatches) {
  96. _topicBatches = [archivedBatches mutableCopy];
  97. [FIRMessagingPendingTopicsList pruneTopicBatches:_topicBatches];
  98. }
  99. _topicsInFlight = [NSMutableSet set];
  100. }
  101. return self;
  102. }
  103. #pragma mark Getters
  104. - (NSUInteger)numberOfBatches {
  105. return self.topicBatches.count;
  106. }
  107. #pragma mark Adding/Removing topics
  108. - (void)addOperationForTopic:(NSString *)topic
  109. withAction:(FIRMessagingTopicAction)action
  110. completion:(nullable FIRMessagingTopicOperationCompletion)completion {
  111. FIRMessagingTopicBatch *lastBatch = nil;
  112. @synchronized (self) {
  113. lastBatch = self.topicBatches.lastObject;
  114. if (!lastBatch || lastBatch.action != action) {
  115. // There either was no last batch, or our last batch's action was not the same, so we have to
  116. // create a new batch
  117. lastBatch = [[FIRMessagingTopicBatch alloc] initWithAction:action];
  118. [self.topicBatches addObject:lastBatch];
  119. }
  120. BOOL topicExistedBefore = ([lastBatch.topics member:topic] != nil);
  121. if (!topicExistedBefore) {
  122. [lastBatch.topics addObject:topic];
  123. [self.delegate pendingTopicsListDidUpdate:self];
  124. }
  125. // Add the completion handler to the batch
  126. if (completion) {
  127. NSMutableArray *handlers = lastBatch.topicHandlers[topic];
  128. if (!handlers) {
  129. handlers = [NSMutableArray arrayWithCapacity:1];
  130. }
  131. [handlers addObject:completion];
  132. }
  133. if (!self.currentBatch) {
  134. self.currentBatch = lastBatch;
  135. }
  136. // This may have been the first topic added, or was added to an ongoing batch
  137. if (self.currentBatch == lastBatch && !topicExistedBefore) {
  138. // Add this topic to our ongoing operations
  139. FIRMessaging_WEAKIFY(self);
  140. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  141. FIRMessaging_STRONGIFY(self);
  142. [self resumeOperationsIfNeeded];
  143. });
  144. }
  145. }
  146. }
  147. - (void)resumeOperationsIfNeeded {
  148. @synchronized (self) {
  149. // If current batch is not set, set it now
  150. if (!self.currentBatch) {
  151. self.currentBatch = self.topicBatches.firstObject;
  152. }
  153. if (self.currentBatch.topics.count == 0) {
  154. return;
  155. }
  156. if (!self.delegate) {
  157. FIRMessagingLoggerError(kFIRMessagingMessageCodePendingTopicsList000,
  158. @"Attempted to update pending topics without a delegate");
  159. return;
  160. }
  161. if (![self.delegate pendingTopicsListCanRequestTopicUpdates:self]) {
  162. return;
  163. }
  164. for (NSString *topic in self.currentBatch.topics) {
  165. if ([self.topicsInFlight member:topic]) {
  166. // This topic is already active, so skip
  167. continue;
  168. }
  169. [self beginUpdateForCurrentBatchTopic:topic];
  170. }
  171. }
  172. }
  173. - (BOOL)subscriptionErrorIsRecoverable:(NSError *)error {
  174. return [error.domain isEqualToString:NSURLErrorDomain];
  175. }
  176. - (void)beginUpdateForCurrentBatchTopic:(NSString *)topic {
  177. @synchronized (self) {
  178. [self.topicsInFlight addObject:topic];
  179. }
  180. FIRMessaging_WEAKIFY(self);
  181. [self.delegate pendingTopicsList:self
  182. requestedUpdateForTopic:topic
  183. action:self.currentBatch.action
  184. completion:^(FIRMessagingTopicOperationResult result, NSError * error) {
  185. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  186. FIRMessaging_STRONGIFY(self);
  187. @synchronized (self) {
  188. [self.topicsInFlight removeObject:topic];
  189. BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
  190. if (result == FIRMessagingTopicOperationResultSucceeded ||
  191. result == FIRMessagingTopicOperationResultCancelled ||
  192. !recoverableError) {
  193. // Notify our handlers and remove the topic from our batch
  194. NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
  195. if (handlers.count) {
  196. dispatch_async(dispatch_get_main_queue(), ^{
  197. for (FIRMessagingTopicOperationCompletion handler in handlers) {
  198. handler(result, error);
  199. }
  200. [handlers removeAllObjects];
  201. });
  202. }
  203. [self.currentBatch.topics removeObject:topic];
  204. [self.currentBatch.topicHandlers removeObjectForKey:topic];
  205. if (self.currentBatch.topics.count == 0) {
  206. // All topic updates successfully finished in this batch, move on to the next batch
  207. [self.topicBatches removeObject:self.currentBatch];
  208. self.currentBatch = nil;
  209. }
  210. [self.delegate pendingTopicsListDidUpdate:self];
  211. FIRMessaging_WEAKIFY(self)
  212. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  213. FIRMessaging_STRONGIFY(self)
  214. [self resumeOperationsIfNeeded];
  215. });
  216. }
  217. }
  218. });
  219. }];
  220. }
  221. @end