FSTRemoteEvent.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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 "Firestore/Source/Remote/FSTRemoteEvent.h"
  17. #include <map>
  18. #include <utility>
  19. #import "Firestore/Source/Core/FSTSnapshotVersion.h"
  20. #import "Firestore/Source/Model/FSTDocument.h"
  21. #import "Firestore/Source/Remote/FSTWatchChange.h"
  22. #import "Firestore/Source/Util/FSTAssert.h"
  23. #import "Firestore/Source/Util/FSTClasses.h"
  24. #import "Firestore/Source/Util/FSTLogger.h"
  25. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  26. using firebase::firestore::model::DocumentKey;
  27. NS_ASSUME_NONNULL_BEGIN
  28. #pragma mark - FSTTargetMapping
  29. @interface FSTTargetMapping ()
  30. /** Private mutator method to add a document key to the mapping */
  31. - (void)addDocumentKey:(const DocumentKey &)documentKey;
  32. /** Private mutator method to remove a document key from the mapping */
  33. - (void)removeDocumentKey:(const DocumentKey &)documentKey;
  34. @end
  35. @implementation FSTTargetMapping
  36. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  37. @throw FSTAbstractMethodException(); // NOLINT
  38. }
  39. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  40. @throw FSTAbstractMethodException(); // NOLINT
  41. }
  42. @end
  43. #pragma mark - FSTResetMapping
  44. @interface FSTResetMapping ()
  45. @property(nonatomic, strong) FSTDocumentKeySet *documents;
  46. @end
  47. @implementation FSTResetMapping
  48. + (instancetype)mappingWithDocuments:(NSArray<FSTDocument *> *)documents {
  49. FSTResetMapping *mapping = [[FSTResetMapping alloc] init];
  50. for (FSTDocument *doc in documents) {
  51. mapping.documents = [mapping.documents setByAddingObject:doc.key];
  52. }
  53. return mapping;
  54. }
  55. - (instancetype)init {
  56. self = [super init];
  57. if (self) {
  58. _documents = [FSTDocumentKeySet keySet];
  59. }
  60. return self;
  61. }
  62. - (BOOL)isEqual:(id)other {
  63. if (other == self) {
  64. return YES;
  65. }
  66. if (![other isMemberOfClass:[FSTResetMapping class]]) {
  67. return NO;
  68. }
  69. FSTResetMapping *otherMapping = (FSTResetMapping *)other;
  70. return [self.documents isEqual:otherMapping.documents];
  71. }
  72. - (NSUInteger)hash {
  73. return self.documents.hash;
  74. }
  75. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  76. self.documents = [self.documents setByAddingObject:documentKey];
  77. }
  78. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  79. self.documents = [self.documents setByRemovingObject:documentKey];
  80. }
  81. @end
  82. #pragma mark - FSTUpdateMapping
  83. @interface FSTUpdateMapping ()
  84. @property(nonatomic, strong) FSTDocumentKeySet *addedDocuments;
  85. @property(nonatomic, strong) FSTDocumentKeySet *removedDocuments;
  86. @end
  87. @implementation FSTUpdateMapping
  88. + (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added
  89. removedDocuments:(NSArray<FSTDocument *> *)removed {
  90. FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init];
  91. for (FSTDocument *doc in added) {
  92. mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key];
  93. }
  94. for (FSTDocument *doc in removed) {
  95. mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key];
  96. }
  97. return mapping;
  98. }
  99. - (instancetype)init {
  100. self = [super init];
  101. if (self) {
  102. _addedDocuments = [FSTDocumentKeySet keySet];
  103. _removedDocuments = [FSTDocumentKeySet keySet];
  104. }
  105. return self;
  106. }
  107. - (BOOL)isEqual:(id)other {
  108. if (other == self) {
  109. return YES;
  110. }
  111. if (![other isMemberOfClass:[FSTUpdateMapping class]]) {
  112. return NO;
  113. }
  114. FSTUpdateMapping *otherMapping = (FSTUpdateMapping *)other;
  115. return [self.addedDocuments isEqual:otherMapping.addedDocuments] &&
  116. [self.removedDocuments isEqual:otherMapping.removedDocuments];
  117. }
  118. - (NSUInteger)hash {
  119. return self.addedDocuments.hash * 31 + self.removedDocuments.hash;
  120. }
  121. - (FSTDocumentKeySet *)applyTo:(FSTDocumentKeySet *)keys {
  122. __block FSTDocumentKeySet *result = keys;
  123. [self.addedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
  124. result = [result setByAddingObject:key];
  125. }];
  126. [self.removedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
  127. result = [result setByRemovingObject:key];
  128. }];
  129. return result;
  130. }
  131. - (void)addDocumentKey:(const DocumentKey &)documentKey {
  132. self.addedDocuments = [self.addedDocuments setByAddingObject:documentKey];
  133. self.removedDocuments = [self.removedDocuments setByRemovingObject:documentKey];
  134. }
  135. - (void)removeDocumentKey:(const DocumentKey &)documentKey {
  136. self.addedDocuments = [self.addedDocuments setByRemovingObject:documentKey];
  137. self.removedDocuments = [self.removedDocuments setByAddingObject:documentKey];
  138. }
  139. @end
  140. #pragma mark - FSTTargetChange
  141. @interface FSTTargetChange ()
  142. @property(nonatomic, assign) FSTCurrentStatusUpdate currentStatusUpdate;
  143. @property(nonatomic, strong, nullable) FSTTargetMapping *mapping;
  144. @property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion;
  145. @property(nonatomic, strong) NSData *resumeToken;
  146. @end
  147. @implementation FSTTargetChange
  148. - (instancetype)init {
  149. if (self = [super init]) {
  150. _currentStatusUpdate = FSTCurrentStatusUpdateNone;
  151. _resumeToken = [NSData data];
  152. }
  153. return self;
  154. }
  155. + (instancetype)changeWithDocuments:(NSArray<FSTMaybeDocument *> *)docs
  156. currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
  157. FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init];
  158. for (FSTMaybeDocument *doc in docs) {
  159. if ([doc isKindOfClass:[FSTDeletedDocument class]]) {
  160. mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key];
  161. } else {
  162. mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key];
  163. }
  164. }
  165. FSTTargetChange *change = [[FSTTargetChange alloc] init];
  166. change.mapping = mapping;
  167. change.currentStatusUpdate = currentStatusUpdate;
  168. return change;
  169. }
  170. + (instancetype)changeWithMapping:(FSTTargetMapping *)mapping
  171. snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
  172. currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
  173. FSTTargetChange *change = [[FSTTargetChange alloc] init];
  174. change.mapping = mapping;
  175. change.snapshotVersion = snapshotVersion;
  176. change.currentStatusUpdate = currentStatusUpdate;
  177. return change;
  178. }
  179. - (FSTTargetMapping *)mapping {
  180. if (!_mapping) {
  181. // Create an FSTUpdateMapping by default, since resets are always explicit
  182. _mapping = [[FSTUpdateMapping alloc] init];
  183. }
  184. return _mapping;
  185. }
  186. /**
  187. * Sets the resume token but only when it has a new value. Empty resumeTokens are
  188. * discarded.
  189. */
  190. - (void)setResumeToken:(NSData *)resumeToken {
  191. if (resumeToken.length > 0) {
  192. _resumeToken = resumeToken;
  193. }
  194. }
  195. @end
  196. #pragma mark - FSTRemoteEvent
  197. @interface FSTRemoteEvent () {
  198. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges;
  199. }
  200. - (instancetype)
  201. initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
  202. targetChanges:(NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges
  203. documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates;
  204. @property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion;
  205. @end
  206. @implementation FSTRemoteEvent {
  207. std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
  208. }
  209. + (instancetype)
  210. eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
  211. targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
  212. documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
  213. return [[FSTRemoteEvent alloc] initWithSnapshotVersion:snapshotVersion
  214. targetChanges:targetChanges
  215. documentUpdates:std::move(documentUpdates)];
  216. }
  217. - (instancetype)initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
  218. targetChanges:
  219. (NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
  220. documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
  221. self = [super init];
  222. if (self) {
  223. _snapshotVersion = snapshotVersion;
  224. _targetChanges = targetChanges;
  225. _documentUpdates = std::move(documentUpdates);
  226. }
  227. return self;
  228. }
  229. - (void)filterUpdatesFromTargetChange:(FSTTargetChange *)targetChange
  230. existingDocuments:(FSTDocumentKeySet *)existingDocuments {
  231. if ([targetChange.mapping isKindOfClass:[FSTUpdateMapping class]]) {
  232. FSTUpdateMapping *update = (FSTUpdateMapping *)targetChange.mapping;
  233. FSTDocumentKeySet *added = update.addedDocuments;
  234. __block FSTDocumentKeySet *result = added;
  235. [added enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) {
  236. if ([existingDocuments containsObject:docKey]) {
  237. result = [result setByRemovingObject:docKey];
  238. }
  239. }];
  240. update.addedDocuments = result;
  241. }
  242. }
  243. - (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange
  244. key:(const DocumentKey &)key {
  245. if (targetChange.currentStatusUpdate == FSTCurrentStatusUpdateMarkCurrent &&
  246. _documentUpdates.find(key) == _documentUpdates.end()) {
  247. // When listening to a query the server responds with a snapshot containing documents
  248. // matching the query and a current marker telling us we're now in sync. It's possible for
  249. // these to arrive as separate remote events or as a single remote event. For a document
  250. // query, there will be no documents sent in the response if the document doesn't exist.
  251. //
  252. // If the snapshot arrives separately from the current marker, we handle it normally and
  253. // updateTrackedLimboDocumentsWithChanges:targetID: will resolve the limbo status of the
  254. // document, removing it from limboDocumentRefs. This works because clients only initiate
  255. // limbo resolution when a target is current and because all current targets are always at a
  256. // consistent snapshot.
  257. //
  258. // However, if the document doesn't exist and the current marker arrives, the document is
  259. // not present in the snapshot and our normal view handling would consider the document to
  260. // remain in limbo indefinitely because there are no updates to the document. To avoid this,
  261. // we specially handle this just this case here: synthesizing a delete.
  262. //
  263. // TODO(dimond): Ideally we would have an explicit lookup query instead resulting in an
  264. // explicit delete message and we could remove this special logic.
  265. _documentUpdates[key] = [FSTDeletedDocument documentWithKey:key version:_snapshotVersion];
  266. }
  267. }
  268. - (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
  269. return static_cast<NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *>(_targetChanges);
  270. }
  271. - (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
  272. return _documentUpdates;
  273. }
  274. /** Adds a document update to this remote event */
  275. - (void)addDocumentUpdate:(FSTMaybeDocument *)document {
  276. _documentUpdates[document.key] = document;
  277. }
  278. /** Handles an existence filter mismatch */
  279. - (void)handleExistenceFilterMismatchForTargetID:(FSTBoxedTargetID *)targetID {
  280. // An existence filter mismatch will reset the query and we need to reset the mapping to contain
  281. // no documents and an empty resume token.
  282. //
  283. // Note:
  284. // * The reset mapping is empty, specifically forcing the consumer of the change to
  285. // forget all keys for this targetID;
  286. // * The resume snapshot for this target must be reset
  287. // * The target must be unacked because unwatching and rewatching introduces a race for
  288. // changes.
  289. //
  290. // TODO(dimond): keep track of reset targets not to raise.
  291. FSTTargetChange *targetChange =
  292. [FSTTargetChange changeWithMapping:[[FSTResetMapping alloc] init]
  293. snapshotVersion:[FSTSnapshotVersion noVersion]
  294. currentStatusUpdate:FSTCurrentStatusUpdateMarkNotCurrent];
  295. _targetChanges[targetID] = targetChange;
  296. }
  297. @end
  298. #pragma mark - FSTWatchChangeAggregator
  299. @interface FSTWatchChangeAggregator ()
  300. /** The snapshot version for every target change this creates. */
  301. @property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
  302. /** Keeps track of the current target mappings */
  303. @property(nonatomic, strong, readonly)
  304. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges;
  305. /** The set of open listens on the client */
  306. @property(nonatomic, strong, readonly)
  307. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *listenTargets;
  308. /** Whether this aggregator was frozen and can no longer be modified */
  309. @property(nonatomic, assign) BOOL frozen;
  310. @end
  311. @implementation FSTWatchChangeAggregator {
  312. NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters;
  313. /** Keeps track of document to update */
  314. std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
  315. }
  316. - (instancetype)
  317. initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
  318. listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets
  319. pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses {
  320. self = [super init];
  321. if (self) {
  322. _snapshotVersion = snapshotVersion;
  323. _frozen = NO;
  324. _targetChanges = [NSMutableDictionary dictionary];
  325. _listenTargets = listenTargets;
  326. _pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses];
  327. _existenceFilters = [NSMutableDictionary dictionary];
  328. }
  329. return self;
  330. }
  331. - (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters {
  332. return static_cast<NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *>(_existenceFilters);
  333. }
  334. - (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID {
  335. FSTTargetChange *change = self.targetChanges[targetID];
  336. if (!change) {
  337. change = [[FSTTargetChange alloc] init];
  338. change.snapshotVersion = self.snapshotVersion;
  339. self.targetChanges[targetID] = change;
  340. }
  341. return change;
  342. }
  343. - (void)addWatchChanges:(NSArray<FSTWatchChange *> *)watchChanges {
  344. FSTAssert(!self.frozen, @"Trying to modify frozen FSTWatchChangeAggregator");
  345. for (FSTWatchChange *watchChange in watchChanges) {
  346. [self addWatchChange:watchChange];
  347. }
  348. }
  349. - (void)addWatchChange:(FSTWatchChange *)watchChange {
  350. FSTAssert(!self.frozen, @"Trying to modify frozen FSTWatchChangeAggregator");
  351. if ([watchChange isKindOfClass:[FSTDocumentWatchChange class]]) {
  352. [self addDocumentChange:(FSTDocumentWatchChange *)watchChange];
  353. } else if ([watchChange isKindOfClass:[FSTWatchTargetChange class]]) {
  354. [self addTargetChange:(FSTWatchTargetChange *)watchChange];
  355. } else if ([watchChange isKindOfClass:[FSTExistenceFilterWatchChange class]]) {
  356. [self addExistenceFilterChange:(FSTExistenceFilterWatchChange *)watchChange];
  357. } else {
  358. FSTFail(@"Unknown watch change: %@", watchChange);
  359. }
  360. }
  361. - (void)addDocumentChange:(FSTDocumentWatchChange *)docChange {
  362. BOOL relevant = NO;
  363. for (FSTBoxedTargetID *targetID in docChange.updatedTargetIDs) {
  364. if ([self isActiveTarget:targetID]) {
  365. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  366. [change.mapping addDocumentKey:docChange.documentKey];
  367. relevant = YES;
  368. }
  369. }
  370. for (FSTBoxedTargetID *targetID in docChange.removedTargetIDs) {
  371. if ([self isActiveTarget:targetID]) {
  372. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  373. [change.mapping removeDocumentKey:docChange.documentKey];
  374. relevant = YES;
  375. }
  376. }
  377. // Only update the document if there is a new document to replace, this might be just a target
  378. // update instead.
  379. if (docChange.document && relevant) {
  380. _documentUpdates[docChange.documentKey] = docChange.document;
  381. }
  382. }
  383. - (void)addTargetChange:(FSTWatchTargetChange *)targetChange {
  384. for (FSTBoxedTargetID *targetID in targetChange.targetIDs) {
  385. FSTTargetChange *change = [self targetChangeForTargetID:targetID];
  386. switch (targetChange.state) {
  387. case FSTWatchTargetChangeStateNoChange:
  388. if ([self isActiveTarget:targetID]) {
  389. // Creating the change above satisfies the semantics of no-change.
  390. change.resumeToken = targetChange.resumeToken;
  391. }
  392. break;
  393. case FSTWatchTargetChangeStateAdded:
  394. [self recordResponseForTargetID:targetID];
  395. if (![self.pendingTargetResponses objectForKey:targetID]) {
  396. // We have a freshly added target, so we need to reset any state that we had previously
  397. // This can happen e.g. when remove and add back a target for existence filter
  398. // mismatches.
  399. change.mapping = nil;
  400. change.currentStatusUpdate = FSTCurrentStatusUpdateNone;
  401. [_existenceFilters removeObjectForKey:targetID];
  402. }
  403. change.resumeToken = targetChange.resumeToken;
  404. break;
  405. case FSTWatchTargetChangeStateRemoved:
  406. // We need to keep track of removed targets to we can post-filter and remove any target
  407. // changes.
  408. [self recordResponseForTargetID:targetID];
  409. FSTAssert(!targetChange.cause, @"WatchChangeAggregator does not handle errored targets.");
  410. break;
  411. case FSTWatchTargetChangeStateCurrent:
  412. if ([self isActiveTarget:targetID]) {
  413. change.currentStatusUpdate = FSTCurrentStatusUpdateMarkCurrent;
  414. change.resumeToken = targetChange.resumeToken;
  415. }
  416. break;
  417. case FSTWatchTargetChangeStateReset:
  418. if ([self isActiveTarget:targetID]) {
  419. // Overwrite any existing target mapping with a reset mapping. Every subsequent update
  420. // will modify the reset mapping, not an update mapping.
  421. change.mapping = [[FSTResetMapping alloc] init];
  422. change.resumeToken = targetChange.resumeToken;
  423. }
  424. break;
  425. default:
  426. FSTWarn(@"Unknown target watch change type: %ld", (long)targetChange.state);
  427. }
  428. }
  429. }
  430. /**
  431. * Records that we got a watch target add/remove by decrementing the number of pending target
  432. * responses that we have.
  433. */
  434. - (void)recordResponseForTargetID:(FSTBoxedTargetID *)targetID {
  435. NSNumber *count = [self.pendingTargetResponses objectForKey:targetID];
  436. int newCount = count ? [count intValue] - 1 : -1;
  437. if (newCount == 0) {
  438. [self.pendingTargetResponses removeObjectForKey:targetID];
  439. } else {
  440. [self.pendingTargetResponses setObject:[NSNumber numberWithInt:newCount] forKey:targetID];
  441. }
  442. }
  443. /**
  444. * Returns true if the given targetId is active. Active targets are those for which there are no
  445. * pending requests to add a listen and are in the current list of targets the client cares about.
  446. *
  447. * Clients can repeatedly listen and stop listening to targets, so this check is useful in
  448. * preventing in preventing race conditions for a target where events arrive but the server hasn't
  449. * yet acknowledged the intended change in state.
  450. */
  451. - (BOOL)isActiveTarget:(FSTBoxedTargetID *)targetID {
  452. return [self.listenTargets objectForKey:targetID] &&
  453. ![self.pendingTargetResponses objectForKey:targetID];
  454. }
  455. - (void)addExistenceFilterChange:(FSTExistenceFilterWatchChange *)existenceFilterChange {
  456. FSTBoxedTargetID *targetID = @(existenceFilterChange.targetID);
  457. if ([self isActiveTarget:targetID]) {
  458. _existenceFilters[targetID] = existenceFilterChange.filter;
  459. }
  460. }
  461. - (FSTRemoteEvent *)remoteEvent {
  462. NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges = self.targetChanges;
  463. NSMutableArray *targetsToRemove = [NSMutableArray array];
  464. // Apply any inactive targets.
  465. for (FSTBoxedTargetID *targetID in [targetChanges keyEnumerator]) {
  466. if (![self isActiveTarget:targetID]) {
  467. [targetsToRemove addObject:targetID];
  468. }
  469. }
  470. [targetChanges removeObjectsForKeys:targetsToRemove];
  471. // Mark this aggregator as frozen so no further modifications are made.
  472. self.frozen = YES;
  473. return [FSTRemoteEvent eventWithSnapshotVersion:self.snapshotVersion
  474. targetChanges:targetChanges
  475. documentUpdates:_documentUpdates];
  476. }
  477. @end
  478. NS_ASSUME_NONNULL_END