You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
9.4 KiB

6 years ago
6 years ago
6 years ago
6 years ago
  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 "Firebase/Messaging/FIRMessagingPendingTopicsList.h"
  17. #import "Firebase/Messaging/FIRMessaging_Private.h"
  18. #import "Firebase/Messaging/FIRMessagingLogger.h"
  19. #import "Firebase/Messaging/FIRMessagingPubSub.h"
  20. #import "Firebase/Messaging/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 alloc] init];
  130. }
  131. [handlers addObject:completion];
  132. lastBatch.topicHandlers[topic] = handlers;
  133. }
  134. if (!self.currentBatch) {
  135. self.currentBatch = lastBatch;
  136. }
  137. // This may have been the first topic added, or was added to an ongoing batch
  138. if (self.currentBatch == lastBatch && !topicExistedBefore) {
  139. // Add this topic to our ongoing operations
  140. FIRMessaging_WEAKIFY(self);
  141. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  142. FIRMessaging_STRONGIFY(self);
  143. [self resumeOperationsIfNeeded];
  144. });
  145. }
  146. }
  147. }
  148. - (void)resumeOperationsIfNeeded {
  149. @synchronized (self) {
  150. // If current batch is not set, set it now
  151. if (!self.currentBatch) {
  152. self.currentBatch = self.topicBatches.firstObject;
  153. }
  154. if (self.currentBatch.topics.count == 0) {
  155. return;
  156. }
  157. if (!self.delegate) {
  158. FIRMessagingLoggerError(kFIRMessagingMessageCodePendingTopicsList000,
  159. @"Attempted to update pending topics without a delegate");
  160. return;
  161. }
  162. if (![self.delegate pendingTopicsListCanRequestTopicUpdates:self]) {
  163. return;
  164. }
  165. for (NSString *topic in self.currentBatch.topics) {
  166. if ([self.topicsInFlight member:topic]) {
  167. // This topic is already active, so skip
  168. continue;
  169. }
  170. [self beginUpdateForCurrentBatchTopic:topic];
  171. }
  172. }
  173. }
  174. - (BOOL)subscriptionErrorIsRecoverable:(NSError *)error {
  175. return [error.domain isEqualToString:NSURLErrorDomain];
  176. }
  177. - (void)beginUpdateForCurrentBatchTopic:(NSString *)topic {
  178. @synchronized (self) {
  179. [self.topicsInFlight addObject:topic];
  180. }
  181. FIRMessaging_WEAKIFY(self);
  182. [self.delegate
  183. pendingTopicsList:self
  184. requestedUpdateForTopic:topic
  185. action:self.currentBatch.action
  186. completion:^(NSError *error) {
  187. dispatch_async(
  188. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  189. FIRMessaging_STRONGIFY(self);
  190. @synchronized(self) {
  191. [self.topicsInFlight removeObject:topic];
  192. BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
  193. if (!error || !recoverableError) {
  194. // Notify our handlers and remove the topic from our batch
  195. NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
  196. if (handlers.count) {
  197. dispatch_async(dispatch_get_main_queue(), ^{
  198. for (FIRMessagingTopicOperationCompletion handler in handlers) {
  199. handler(error);
  200. }
  201. [handlers removeAllObjects];
  202. });
  203. }
  204. [self.currentBatch.topics removeObject:topic];
  205. [self.currentBatch.topicHandlers removeObjectForKey:topic];
  206. if (self.currentBatch.topics.count == 0) {
  207. // All topic updates successfully finished in this batch, move on
  208. // to the next batch
  209. [self.topicBatches removeObject:self.currentBatch];
  210. self.currentBatch = nil;
  211. }
  212. [self.delegate pendingTopicsListDidUpdate:self];
  213. FIRMessaging_WEAKIFY(self);
  214. dispatch_async(
  215. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
  216. ^{
  217. FIRMessaging_STRONGIFY(self);
  218. [self resumeOperationsIfNeeded];
  219. });
  220. }
  221. }
  222. });
  223. }];
  224. }
  225. @end