FIRMessagingPendingTopicsList.m 9.7 KB

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