aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/Source')
-rw-r--r--Firestore/Source/API/FIRCollectionReference.mm24
-rw-r--r--Firestore/Source/API/FIRDocumentChange.m22
-rw-r--r--Firestore/Source/API/FIRDocumentReference.m4
-rw-r--r--Firestore/Source/API/FIRDocumentSnapshot.m132
-rw-r--r--Firestore/Source/API/FIRFieldPath.m2
-rw-r--r--Firestore/Source/API/FIRFirestore.m115
-rw-r--r--Firestore/Source/API/FIRQuery.m91
-rw-r--r--Firestore/Source/API/FIRQuerySnapshot.m44
-rw-r--r--Firestore/Source/API/FIRSetOptions.m1
-rw-r--r--Firestore/Source/API/FIRSnapshotMetadata.m22
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions+Internal.h38
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions.m72
-rw-r--r--Firestore/Source/API/FIRWriteBatch.m6
-rw-r--r--Firestore/Source/Core/FSTEventManager.h2
-rw-r--r--Firestore/Source/Core/FSTEventManager.m8
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.h2
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.m7
-rw-r--r--Firestore/Source/Core/FSTQuery.m4
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.h3
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.m15
-rw-r--r--Firestore/Source/Core/FSTTypes.h9
-rw-r--r--Firestore/Source/Core/FSTView.h7
-rw-r--r--Firestore/Source/Core/FSTView.m24
-rw-r--r--Firestore/Source/Local/FSTDocumentReference.m2
-rw-r--r--Firestore/Source/Local/FSTLevelDB.mm4
-rw-r--r--Firestore/Source/Model/FSTDatabaseID.m2
-rw-r--r--Firestore/Source/Model/FSTFieldValue.h103
-rw-r--r--Firestore/Source/Model/FSTFieldValue.m86
-rw-r--r--Firestore/Source/Model/FSTMutation.h16
-rw-r--r--Firestore/Source/Model/FSTMutation.m56
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.m2
-rw-r--r--Firestore/Source/Model/FSTPath.m2
-rw-r--r--Firestore/Source/Public/FIRDocumentChange.h4
-rw-r--r--Firestore/Source/Public/FIRDocumentReference.h2
-rw-r--r--Firestore/Source/Public/FIRDocumentSnapshot.h143
-rw-r--r--Firestore/Source/Public/FIRFirestore.h17
-rw-r--r--Firestore/Source/Public/FIRQuery.h13
-rw-r--r--Firestore/Source/Public/FIRQuerySnapshot.h4
-rw-r--r--Firestore/Source/Public/FIRWriteBatch.h7
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.h2
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.m120
-rw-r--r--Firestore/Source/Remote/FSTStream.m2
42 files changed, 988 insertions, 253 deletions
diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm
index 92cccc6..70a14c2 100644
--- a/Firestore/Source/API/FIRCollectionReference.mm
+++ b/Firestore/Source/API/FIRCollectionReference.mm
@@ -15,6 +15,7 @@
*/
#import "FIRCollectionReference.h"
+#import "FIRFirestore.h"
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
@@ -65,6 +66,29 @@ NS_ASSUME_NONNULL_BEGIN
FSTFail(@"Use FIRCollectionReference initWithPath: initializer.");
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToReference:other];
+}
+
+- (BOOL)isEqualToReference:(nullable FIRCollectionReference *)reference {
+ if (self == reference) return YES;
+ if (reference == nil) return NO;
+ if (self.firestore != reference.firestore && ![self.firestore isEqual:reference.firestore])
+ return NO;
+ if (self.query != reference.query && ![self.query isEqual:reference.query]) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.query hash];
+ return hash;
+}
+
- (NSString *)collectionID {
return [self.query.path lastSegment];
}
diff --git a/Firestore/Source/API/FIRDocumentChange.m b/Firestore/Source/API/FIRDocumentChange.m
index 970dc90..d1d9999 100644
--- a/Firestore/Source/API/FIRDocumentChange.m
+++ b/Firestore/Source/API/FIRDocumentChange.m
@@ -57,11 +57,11 @@ NS_ASSUME_NONNULL_BEGIN
NSUInteger index = 0;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
FSTAssert(change.type == FSTDocumentViewChangeTypeAdded,
@"Invalid event type for first snapshot");
FSTAssert(!lastDocument ||
@@ -79,11 +79,11 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocumentSet *indexTracker = snapshot.oldDocuments;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
NSUInteger oldIndex = NSNotFound;
NSUInteger newIndex = NSNotFound;
@@ -112,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRDocumentChange
- (instancetype)initWithType:(FIRDocumentChangeType)type
- document:(FIRDocumentSnapshot *)document
+ document:(FIRQueryDocumentSnapshot *)document
oldIndex:(NSUInteger)oldIndex
newIndex:(NSUInteger)newIndex {
if (self = [super init]) {
diff --git a/Firestore/Source/API/FIRDocumentReference.m b/Firestore/Source/API/FIRDocumentReference.m
index 1c80ea9..87e6631 100644
--- a/Firestore/Source/API/FIRDocumentReference.m
+++ b/Firestore/Source/API/FIRDocumentReference.m
@@ -48,6 +48,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
NS_DESIGNATED_INITIALIZER;
+@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
+
@end
@implementation FIRDocumentListenOptions
@@ -114,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToReference:other];
}
diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m
index b78472e..358ddac 100644
--- a/Firestore/Source/API/FIRDocumentSnapshot.m
+++ b/Firestore/Source/API/FIRDocumentSnapshot.m
@@ -20,11 +20,13 @@
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
NS_ASSUME_NONNULL_BEGIN
@@ -76,6 +78,37 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ // self class could be FIRDocumentSnapshot or subtype. So we compare with base type explicitly.
+ if (![other isKindOfClass:[FIRDocumentSnapshot class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRDocumentSnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+ if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore])
+ return NO;
+ if (self.internalKey != snapshot.internalKey && ![self.internalKey isEqual:snapshot.internalKey])
+ return NO;
+ if (self.internalDocument != snapshot.internalDocument &&
+ ![self.internalDocument isEqual:snapshot.internalDocument])
+ return NO;
+ if (self.fromCache != snapshot.fromCache) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.internalKey hash];
+ hash = hash * 31u + [self.internalDocument hash];
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
@dynamic exists;
- (BOOL)exists {
@@ -99,40 +132,48 @@ NS_ASSUME_NONNULL_BEGIN
return _cachedMetadata;
}
-- (NSDictionary<NSString *, id> *)data {
- FSTDocument *document = self.internalDocument;
-
- if (!document) {
- FSTThrowInvalidUsage(
- @"NonExistentDocumentException",
- @"Document '%@' doesn't exist. "
- @"Check document.exists to make sure the document exists before calling document.data.",
- self.internalKey);
- }
+- (nullable NSDictionary<NSString *, id> *)data {
+ return [self dataWithOptions:[FIRSnapshotOptions defaultOptions]];
+}
- return [self convertedObject:[self.internalDocument data]];
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ return self.internalDocument == nil
+ ? nil
+ : [self convertedObject:[self.internalDocument data]
+ options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
}
-- (nullable id)objectForKeyedSubscript:(id)key {
+- (nullable id)valueForField:(id)field {
+ return [self valueForField:field options:[FIRSnapshotOptions defaultOptions]];
+}
+
+- (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options {
FIRFieldPath *fieldPath;
- if ([key isKindOfClass:[NSString class]]) {
- fieldPath = [FIRFieldPath pathWithDotSeparatedString:key];
- } else if ([key isKindOfClass:[FIRFieldPath class]]) {
- fieldPath = key;
+ if ([field isKindOfClass:[NSString class]]) {
+ fieldPath = [FIRFieldPath pathWithDotSeparatedString:field];
+ } else if ([field isKindOfClass:[FIRFieldPath class]]) {
+ fieldPath = field;
} else {
FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath.");
}
FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue];
- return [self convertedValue:fieldValue];
+ return fieldValue == nil
+ ? nil
+ : [self convertedValue:fieldValue
+ options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
}
-- (id)convertedValue:(FSTFieldValue *)value {
+- (nullable id)objectForKeyedSubscript:(id)key {
+ return [self valueForField:key];
+}
+
+- (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)options {
if ([value isKindOfClass:[FSTObjectValue class]]) {
- return [self convertedObject:(FSTObjectValue *)value];
+ return [self convertedObject:(FSTObjectValue *)value options:options];
} else if ([value isKindOfClass:[FSTArrayValue class]]) {
- return [self convertedArray:(FSTArrayValue *)value];
+ return [self convertedArray:(FSTArrayValue *)value options:options];
} else if ([value isKindOfClass:[FSTReferenceValue class]]) {
FSTReferenceValue *ref = (FSTReferenceValue *)value;
FSTDatabaseID *refDatabase = ref.databaseID;
@@ -146,30 +187,69 @@ NS_ASSUME_NONNULL_BEGIN
self.reference.path, refDatabase.projectID, refDatabase.databaseID, database.projectID,
database.databaseID);
}
- return [FIRDocumentReference referenceWithKey:ref.value firestore:self.firestore];
+ return [FIRDocumentReference referenceWithKey:[ref valueWithOptions:options]
+ firestore:self.firestore];
} else {
- return value.value;
+ return [value valueWithOptions:options];
}
}
-- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue {
+- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue
+ options:(FSTFieldValueOptions *)options {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[objectValue.internalValue
enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *value, BOOL *stop) {
- result[key] = [self convertedValue:value];
+ result[key] = [self convertedValue:value options:options];
}];
return result;
}
-- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue {
+- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue
+ options:(FSTFieldValueOptions *)options {
NSArray<FSTFieldValue *> *internalValue = arrayValue.internalValue;
NSMutableArray *result = [NSMutableArray arrayWithCapacity:internalValue.count];
[internalValue enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
- [result addObject:[self convertedValue:value]];
+ [result addObject:[self convertedValue:value options:options]];
}];
return result;
}
@end
+@interface FIRQueryDocumentSnapshot ()
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(FSTDocumentKey *)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRQueryDocumentSnapshot
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(FSTDocumentKey *)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache {
+ self = [super initWithFirestore:firestore
+ documentKey:documentKey
+ document:document
+ fromCache:fromCache];
+ return self;
+}
+
+- (NSDictionary<NSString *, id> *)data {
+ NSDictionary<NSString *, id> *data = [super data];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ NSDictionary<NSString *, id> *data = [super dataWithOptions:options];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFieldPath.m b/Firestore/Source/API/FIRFieldPath.m
index d0a70c0..f4e532f 100644
--- a/Firestore/Source/API/FIRFieldPath.m
+++ b/Firestore/Source/API/FIRFieldPath.m
@@ -80,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN
return [[[self class] alloc] initPrivate:self.internalValue];
}
-- (BOOL)isEqual:(id)object {
+- (BOOL)isEqual:(nullable id)object {
if (self == object) {
return YES;
}
diff --git a/Firestore/Source/API/FIRFirestore.m b/Firestore/Source/API/FIRFirestore.m
index 7814ce1..9df3711 100644
--- a/Firestore/Source/API/FIRFirestore.m
+++ b/Firestore/Source/API/FIRFirestore.m
@@ -50,13 +50,17 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
@property(nonatomic, strong) id<FSTCredentialsProvider> credentialsProvider;
@property(nonatomic, strong) FSTDispatchQueue *workerDispatchQueue;
-@property(nonatomic, strong) FSTFirestoreClient *client;
+// Note that `client` is updated after initialization, but marking this readwrite would generate an
+// incorrect setter (since we make the assignment to `client` inside an `@synchronized` block.
+@property(nonatomic, strong, readonly) FSTFirestoreClient *client;
@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
@end
@implementation FIRFirestore {
+ // All guarded by @synchronized(self)
FIRFirestoreSettings *_settings;
+ FSTFirestoreClient *_client;
}
+ (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
@@ -154,64 +158,74 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
}
- (FIRFirestoreSettings *)settings {
- // Disallow mutation of our internal settings
- return [_settings copy];
+ @synchronized(self) {
+ // Disallow mutation of our internal settings
+ return [_settings copy];
+ }
}
- (void)setSettings:(FIRFirestoreSettings *)settings {
- // As a special exception, don't throw if the same settings are passed repeatedly. This should
- // make it more friendly to create a Firestore instance.
- if (_client && ![_settings isEqual:settings]) {
- FSTThrowInvalidUsage(@"FIRIllegalStateException",
- @"Firestore instance has already been started and its settings can no "
- "longer be changed. You can only set settings before calling any "
- "other methods on a Firestore instance.");
+ @synchronized(self) {
+ // As a special exception, don't throw if the same settings are passed repeatedly. This should
+ // make it more friendly to create a Firestore instance.
+ if (_client && ![_settings isEqual:settings]) {
+ FSTThrowInvalidUsage(@"FIRIllegalStateException",
+ @"Firestore instance has already been started and its settings can no "
+ "longer be changed. You can only set settings before calling any "
+ "other methods on a Firestore instance.");
+ }
+ _settings = [settings copy];
}
- _settings = [settings copy];
}
/**
- * Ensures that the FirestoreClient is configured.
- * @return self
+ * Ensures that the FirestoreClient is configured and returns it.
*/
-- (instancetype)firestoreWithConfiguredClient {
- if (!_client) {
- // These values are validated elsewhere; this is just double-checking:
- FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
- FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
-
- FSTDatabaseInfo *databaseInfo =
- [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
- persistenceKey:_persistenceKey
- host:_settings.host
- sslEnabled:_settings.sslEnabled];
-
- FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
-
- _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
- usePersistence:_settings.persistenceEnabled
- credentialsProvider:_credentialsProvider
- userDispatchQueue:userDispatchQueue
- workerDispatchQueue:_workerDispatchQueue];
+- (FSTFirestoreClient *)client {
+ [self ensureClientConfigured];
+ return _client;
+}
+
+- (void)ensureClientConfigured {
+ @synchronized(self) {
+ if (!_client) {
+ // These values are validated elsewhere; this is just double-checking:
+ FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
+ FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
+
+ FSTDatabaseInfo *databaseInfo =
+ [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
+ persistenceKey:_persistenceKey
+ host:_settings.host
+ sslEnabled:_settings.sslEnabled];
+
+ FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
+
+ _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
+ usePersistence:_settings.persistenceEnabled
+ credentialsProvider:_credentialsProvider
+ userDispatchQueue:userDispatchQueue
+ workerDispatchQueue:_workerDispatchQueue];
+ }
}
- return self;
}
- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
if (!collectionPath) {
FSTThrowInvalidArgument(@"Collection path cannot be nil.");
}
+ [self ensureClientConfigured];
FSTResourcePath *path = [FSTResourcePath pathWithString:collectionPath];
- return
- [FIRCollectionReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
+ return [FIRCollectionReference referenceWithPath:path firestore:self];
}
- (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
if (!documentPath) {
FSTThrowInvalidArgument(@"Document path cannot be nil.");
}
+ [self ensureClientConfigured];
FSTResourcePath *path = [FSTResourcePath pathWithString:documentPath];
- return [FIRDocumentReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
+ return [FIRDocumentReference referenceWithPath:path firestore:self];
}
- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
@@ -241,12 +255,13 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
internalCompletion(result, error);
});
};
- [self firestoreWithConfiguredClient];
[self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
}
- (FIRWriteBatch *)batch {
- return [FIRWriteBatch writeBatchWithFirestore:[self firestoreWithConfiguredClient]];
+ [self ensureClientConfigured];
+
+ return [FIRWriteBatch writeBatchWithFirestore:self];
}
- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
@@ -264,11 +279,19 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
}
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
- if (!self.client) {
+ FSTFirestoreClient *client;
+ @synchronized(self) {
+ client = _client;
+ _client = nil;
+ }
+
+ if (!client) {
+ // We should be dispatching the callback on the user dispatch queue but if the client is nil
+ // here that queue was never created.
completion(nil);
- return;
+ } else {
+ [client shutdownWithCompletion:completion];
}
- return [self.client shutdownWithCompletion:completion];
}
+ (BOOL)isLoggingEnabled {
@@ -279,6 +302,16 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
}
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
+ [self ensureClientConfigured];
+ [self.client enableNetworkWithCompletion:completion];
+}
+
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable))completion {
+ [self ensureClientConfigured];
+ [self.client disableNetworkWithCompletion:completion];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRQuery.m b/Firestore/Source/API/FIRQuery.m
index 12e79c5..2feca39 100644
--- a/Firestore/Source/API/FIRQuery.m
+++ b/Firestore/Source/API/FIRQuery.m
@@ -107,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToQuery:other];
}
@@ -256,6 +256,95 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
value:value];
}
+- (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate {
+ NSComparisonPredicate *comparison = (NSComparisonPredicate *)predicate;
+ if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) {
+ FSTThrowInvalidArgument(@"Invalid query. Predicate cannot have an aggregate modifier.");
+ }
+ NSString *path;
+ id value = nil;
+ if ([comparison.leftExpression expressionType] == NSKeyPathExpressionType &&
+ [comparison.rightExpression expressionType] == NSConstantValueExpressionType) {
+ path = comparison.leftExpression.keyPath;
+ value = comparison.rightExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else if ([comparison.leftExpression expressionType] == NSConstantValueExpressionType &&
+ [comparison.rightExpression expressionType] == NSKeyPathExpressionType) {
+ path = comparison.rightExpression.keyPath;
+ value = comparison.leftExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Predicate comparisons must include a key path and a constant.");
+ }
+ // Fallback cases of unsupported comparison operator.
+ switch (comparison.predicateOperatorType) {
+ case NSCustomSelectorPredicateOperatorType:
+ FSTThrowInvalidArgument(@"Invalid query. Custom predicate filters are not supported.");
+ break;
+ default:
+ FSTThrowInvalidArgument(@"Invalid query. Operator type %lu is not supported.",
+ (unsigned long)comparison.predicateOperatorType);
+ }
+}
+
+- (FIRQuery *)queryFilteredUsingCompoundPredicate:(NSPredicate *)predicate {
+ NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate;
+ if (compound.compoundPredicateType != NSAndPredicateType || compound.subpredicates.count == 0) {
+ FSTThrowInvalidArgument(@"Invalid query. Only compound queries using AND are supported.");
+ }
+ FIRQuery *query = self;
+ for (NSPredicate *pred in compound.subpredicates) {
+ query = [query queryFilteredUsingPredicate:pred];
+ }
+ return query;
+}
+
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate {
+ if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
+ return [self queryFilteredUsingComparisonPredicate:predicate];
+ } else if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
+ return [self queryFilteredUsingCompoundPredicate:predicate];
+ } else if ([predicate isKindOfClass:[[NSPredicate
+ predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
+ return true;
+ }] class]]) {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Block-based predicates are not "
+ "supported. Please use predicateWithFormat to "
+ "create predicates instead.");
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Expect comparison or compound of "
+ "comparison predicate. Please use "
+ "predicateWithFormat to create predicates.");
+ }
+}
+
- (FIRQuery *)queryOrderedByField:(NSString *)field {
return
[self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field] descending:NO];
diff --git a/Firestore/Source/API/FIRQuerySnapshot.m b/Firestore/Source/API/FIRQuerySnapshot.m
index 6bc6761..177e461 100644
--- a/Firestore/Source/API/FIRQuerySnapshot.m
+++ b/Firestore/Source/API/FIRQuerySnapshot.m
@@ -16,6 +16,7 @@
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
+#import "FIRFirestore.h"
#import "FIRSnapshotMetadata.h"
#import "Firestore/Source/API/FIRDocumentChange+Internal.h"
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
@@ -57,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRQuerySnapshot {
// Cached value of the documents property.
- NSArray<FIRDocumentSnapshot *> *_documents;
+ NSArray<FIRQueryDocumentSnapshot *> *_documents;
// Cached value of the documentChanges property.
NSArray<FIRDocumentChange *> *_documentChanges;
@@ -76,6 +77,35 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+ if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore])
+ return NO;
+ if (self.originalQuery != snapshot.originalQuery &&
+ ![self.originalQuery isEqual:snapshot.originalQuery])
+ return NO;
+ if (self.snapshot != snapshot.snapshot && ![self.snapshot isEqual:snapshot.snapshot]) return NO;
+ if (self.metadata != snapshot.metadata && ![self.metadata isEqual:snapshot.metadata]) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.originalQuery hash];
+ hash = hash * 31u + [self.snapshot hash];
+ hash = hash * 31u + [self.metadata hash];
+ return hash;
+}
+
@dynamic empty;
- (FIRQuery *)query {
@@ -93,18 +123,18 @@ NS_ASSUME_NONNULL_BEGIN
return self.snapshot.documents.count;
}
-- (NSArray<FIRDocumentSnapshot *> *)documents {
+- (NSArray<FIRQueryDocumentSnapshot *> *)documents {
if (!_documents) {
FSTDocumentSet *documentSet = self.snapshot.documents;
FIRFirestore *firestore = self.firestore;
BOOL fromCache = self.metadata.fromCache;
- NSMutableArray<FIRDocumentSnapshot *> *result = [NSMutableArray array];
+ NSMutableArray<FIRQueryDocumentSnapshot *> *result = [NSMutableArray array];
for (FSTDocument *document in documentSet.documentEnumerator) {
- [result addObject:[FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:document.key
- document:document
- fromCache:fromCache]];
+ [result addObject:[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:document.key
+ document:document
+ fromCache:fromCache]];
}
_documents = result;
diff --git a/Firestore/Source/API/FIRSetOptions.m b/Firestore/Source/API/FIRSetOptions.m
index 623deaa..743bcc7 100644
--- a/Firestore/Source/API/FIRSetOptions.m
+++ b/Firestore/Source/API/FIRSetOptions.m
@@ -15,7 +15,6 @@
*/
#import "Firestore/Source/API/FIRSetOptions+Internal.h"
-#import "Firestore/Source/Model/FSTMutation.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Source/API/FIRSnapshotMetadata.m b/Firestore/Source/API/FIRSnapshotMetadata.m
index 224015f..d957a8d 100644
--- a/Firestore/Source/API/FIRSnapshotMetadata.m
+++ b/Firestore/Source/API/FIRSnapshotMetadata.m
@@ -44,6 +44,28 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToMetadata:other];
+}
+
+- (BOOL)isEqualToMetadata:(nullable FIRSnapshotMetadata *)metadata {
+ if (self == metadata) return YES;
+ if (metadata == nil) return NO;
+ if (self.pendingWrites != metadata.pendingWrites) return NO;
+ if (self.fromCache != metadata.fromCache) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = self.pendingWrites ? 1 : 0;
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSnapshotOptions+Internal.h b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
new file mode 100644
index 0000000..64e7dbc
--- /dev/null
+++ b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRDocumentSnapshot.h"
+
+#import <Foundation/Foundation.h>
+
+#import "Firestore/Source/Model/FSTFieldValue.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotOptions (Internal)
+
+/** Returns a default instance of FIRSnapshotOptions that specifies no options. */
++ (instancetype)defaultOptions;
+
+/* Initializes a new instance with the specified server timestamp behavior. */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior;
+
+/* Returns the server timestamp behavior. Returns -1 if no behavior is specified. */
+- (FSTServerTimestampBehavior)serverTimestampBehavior;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSnapshotOptions.m b/Firestore/Source/API/FIRSnapshotOptions.m
new file mode 100644
index 0000000..72ea3cc
--- /dev/null
+++ b/Firestore/Source/API/FIRSnapshotOptions.m
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRDocumentSnapshot.h"
+
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotOptions ()
+
+@property(nonatomic) FSTServerTimestampBehavior serverTimestampBehavior;
+
+@end
+
+@implementation FIRSnapshotOptions
+
+- (instancetype)initWithServerTimestampBehavior:
+ (FSTServerTimestampBehavior)serverTimestampBehavior {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ }
+
+ return self;
+}
+
++ (instancetype)defaultOptions {
+ static FIRSnapshotOptions *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ sharedInstance =
+ [[FIRSnapshotOptions alloc] initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
+ });
+
+ return sharedInstance;
+}
+
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
+ switch (serverTimestampBehavior) {
+ case FIRServerTimestampBehaviorEstimate:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate];
+ case FIRServerTimestampBehaviorPrevious:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious];
+ case FIRServerTimestampBehaviorNone:
+ return [FIRSnapshotOptions defaultOptions];
+ default:
+ FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior);
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/API/FIRWriteBatch.m b/Firestore/Source/API/FIRWriteBatch.m
index b918a9a..b1cfa09 100644
--- a/Firestore/Source/API/FIRWriteBatch.m
+++ b/Firestore/Source/API/FIRWriteBatch.m
@@ -93,7 +93,11 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion {
+- (void)commit {
+ [self commitWithCompletion:nil];
+}
+
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
[self verifyNotCommitted];
self.committed = TRUE;
[self.firestore.client writeMutations:self.mutations completion:completion];
diff --git a/Firestore/Source/Core/FSTEventManager.h b/Firestore/Source/Core/FSTEventManager.h
index edd2a96..8eafd4b 100644
--- a/Firestore/Source/Core/FSTEventManager.h
+++ b/Firestore/Source/Core/FSTEventManager.h
@@ -62,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)queryDidChangeViewSnapshot:(FSTViewSnapshot *)snapshot;
- (void)queryDidError:(NSError *)error;
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@property(nonatomic, strong, readonly) FSTQuery *query;
diff --git a/Firestore/Source/Core/FSTEventManager.m b/Firestore/Source/Core/FSTEventManager.m
index 3e1b99b..bc204a0 100644
--- a/Firestore/Source/Core/FSTEventManager.m
+++ b/Firestore/Source/Core/FSTEventManager.m
@@ -151,7 +151,7 @@ NS_ASSUME_NONNULL_BEGIN
self.viewSnapshotHandler(nil, error);
}
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
if (self.snapshot && !self.raisedInitialEvent &&
[self shouldRaiseInitialEventForSnapshot:self.snapshot onlineState:onlineState]) {
@@ -268,7 +268,7 @@ NS_ASSUME_NONNULL_BEGIN
}
[queryInfo.listeners addObject:listener];
- [listener clientDidChangeOnlineState:self.onlineState];
+ [listener applyChangedOnlineState:self.onlineState];
if (queryInfo.viewSnapshot) {
[listener queryDidChangeViewSnapshot:queryInfo.viewSnapshot];
@@ -321,11 +321,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.queries removeObjectForKey:query];
}
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
for (FSTQueryListenersInfo *info in self.queries.objectEnumerator) {
for (FSTQueryListener *listener in info.listeners) {
- [listener clientDidChangeOnlineState:onlineState];
+ [listener applyChangedOnlineState:onlineState];
}
}
}
diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h
index 6a1e11b..0ecf2f6 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.h
+++ b/Firestore/Source/Core/FSTFirestoreClient.h
@@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN
* SDK architecture. It is responsible for creating the worker queue that is shared by all of the
* other components in the system.
*/
-@interface FSTFirestoreClient : NSObject
+@interface FSTFirestoreClient : NSObject <FSTOnlineStateDelegate>
/**
* Creates and returns a FSTFirestoreClient with the given parameters.
diff --git a/Firestore/Source/Core/FSTFirestoreClient.m b/Firestore/Source/Core/FSTFirestoreClient.m
index 2e0e407..fff644d 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.m
+++ b/Firestore/Source/Core/FSTFirestoreClient.m
@@ -172,7 +172,7 @@ NS_ASSUME_NONNULL_BEGIN
// Setup wiring for remote store.
_remoteStore.syncEngine = _syncEngine;
- _remoteStore.onlineStateDelegate = _eventManager;
+ _remoteStore.onlineStateDelegate = self;
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
// queue, etc.) so must be started after LocalStore.
@@ -187,6 +187,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.syncEngine userDidChange:user];
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ [self.syncEngine applyChangedOnlineState:onlineState];
+ [self.eventManager applyChangedOnlineState:onlineState];
+}
+
- (void)disableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore disableNetwork];
diff --git a/Firestore/Source/Core/FSTQuery.m b/Firestore/Source/Core/FSTQuery.m
index 0bfd917..13657f7 100644
--- a/Firestore/Source/Core/FSTQuery.m
+++ b/Firestore/Source/Core/FSTQuery.m
@@ -205,7 +205,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self.field isEqual:((FSTNullFilter *)other).field];
}
@@ -246,7 +246,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self.field isEqual:((FSTNanFilter *)other).field];
}
diff --git a/Firestore/Source/Core/FSTSyncEngine.h b/Firestore/Source/Core/FSTSyncEngine.h
index bb45196..7060155 100644
--- a/Firestore/Source/Core/FSTSyncEngine.h
+++ b/Firestore/Source/Core/FSTSyncEngine.h
@@ -100,6 +100,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)userDidChange:(FSTUser *)user;
+/** Applies an FSTOnlineState change to the sync engine and notifies any views of the change. */
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTSyncEngine.m b/Firestore/Source/Core/FSTSyncEngine.m
index 98658e4..27ab73e 100644
--- a/Firestore/Source/Core/FSTSyncEngine.m
+++ b/Firestore/Source/Core/FSTSyncEngine.m
@@ -318,6 +318,21 @@ NS_ASSUME_NONNULL_BEGIN
[self emitNewSnapshotsWithChanges:changes remoteEvent:remoteEvent];
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ NSMutableArray<FSTViewSnapshot *> *newViewSnapshots = [NSMutableArray array];
+ [self.queryViewsByQuery
+ enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
+ FSTViewChange *viewChange = [queryView.view applyChangedOnlineState:onlineState];
+ FSTAssert(viewChange.limboChanges.count == 0,
+ @"OnlineState should not affect limbo documents.");
+ if (viewChange.snapshot) {
+ [newViewSnapshots addObject:viewChange.snapshot];
+ }
+ }];
+
+ [self.delegate handleViewSnapshots:newViewSnapshots];
+}
+
- (void)rejectListenWithTargetID:(FSTBoxedTargetID *)targetID error:(NSError *)error {
[self assertDelegateExistsForSelector:_cmd];
diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h
index c10f1bf..b47bd0b 100644
--- a/Firestore/Source/Core/FSTTypes.h
+++ b/Firestore/Source/Core/FSTTypes.h
@@ -67,8 +67,8 @@ typedef void (^FSTTransactionBlock)(FSTTransaction *transaction,
typedef NS_ENUM(NSUInteger, FSTOnlineState) {
/**
* The Firestore client is in an unknown online state. This means the client is either not
- * actively trying to establish a connection or it was previously in an unknown state and is
- * trying to establish a connection.
+ * actively trying to establish a connection or it is currently trying to establish a connection,
+ * but it has not succeeded or failed yet.
*/
FSTOnlineStateUnknown,
@@ -80,9 +80,8 @@ typedef NS_ENUM(NSUInteger, FSTOnlineState) {
FSTOnlineStateHealthy,
/**
- * The client has tried to establish a connection but has failed.
- * This state is reached after either a connection attempt failed or a healthy stream was closed
- * for unexpected reasons.
+ * The client considers itself offline. It is either trying to establish a connection but
+ * failing, or it has been explicitly marked offline via a call to `disableNetwork`.
*/
FSTOnlineStateFailed
};
diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h
index ed230a3..6ff77cd 100644
--- a/Firestore/Source/Core/FSTView.h
+++ b/Firestore/Source/Core/FSTView.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
+#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
@@ -138,6 +139,12 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
- (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges
targetChange:(nullable FSTTargetChange *)targetChange;
+/**
+ * Applies an FSTOnlineState change to the view, potentially generating an FSTViewChange if the
+ * view's syncState changes as a result.
+ */
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTView.m b/Firestore/Source/Core/FSTView.m
index 9b44bf4..78019c6 100644
--- a/Firestore/Source/Core/FSTView.m
+++ b/Firestore/Source/Core/FSTView.m
@@ -94,6 +94,12 @@ NS_ASSUME_NONNULL_BEGIN
return self.type == otherChange.type && [self.key isEqual:otherChange.key];
}
+- (NSUInteger)hash {
+ NSUInteger hash = self.type;
+ hash = hash * 31u + [self.key hash];
+ return hash;
+}
+
@end
#pragma mark - FSTViewChange
@@ -330,6 +336,24 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
}
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ if (self.isCurrent && onlineState == FSTOnlineStateFailed) {
+ // If we're offline, set `current` to NO and then call applyChanges to refresh our syncState
+ // and generate an FSTViewChange as appropriate. We are guaranteed to get a new FSTTargetChange
+ // that sets `current` back to YES once the client is back online.
+ self.current = NO;
+ return
+ [self applyChangesToDocuments:[[FSTViewDocumentChanges alloc]
+ initWithDocumentSet:self.documentSet
+ changeSet:[FSTDocumentViewChangeSet changeSet]
+ needsRefill:NO
+ mutatedKeys:self.mutatedKeys]];
+ } else {
+ // No effect, just return a no-op FSTViewChange.
+ return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]];
+ }
+}
+
#pragma mark - Private methods
/** Returns whether the doc for the given key should be in limbo. */
diff --git a/Firestore/Source/Local/FSTDocumentReference.m b/Firestore/Source/Local/FSTDocumentReference.m
index 1631789..25a5935 100644
--- a/Firestore/Source/Local/FSTDocumentReference.m
+++ b/Firestore/Source/Local/FSTDocumentReference.m
@@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
FSTDocumentReference *reference = (FSTDocumentReference *)other;
diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm
index fb1c81a..83b932c 100644
--- a/Firestore/Source/Local/FSTLevelDB.mm
+++ b/Firestore/Source/Local/FSTLevelDB.mm
@@ -72,8 +72,8 @@ using leveldb::WriteOptions;
#else
#error "local storage on tvOS"
-// TODO(mcg): Writing to NSDocumentsDirectory on tvOS will fail; we need to write to Caches
-// https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/
+ // TODO(mcg): Writing to NSDocumentsDirectory on tvOS will fail; we need to write to Caches
+ // https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/
#endif
}
diff --git a/Firestore/Source/Model/FSTDatabaseID.m b/Firestore/Source/Model/FSTDatabaseID.m
index 4d0448a..bff5855 100644
--- a/Firestore/Source/Model/FSTDatabaseID.m
+++ b/Firestore/Source/Model/FSTDatabaseID.m
@@ -48,7 +48,7 @@ NSString *const kDefaultDatabaseID = @"(default)";
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToDatabaseId:other];
}
diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h
index 6de9793..93fd5c4 100644
--- a/Firestore/Source/Model/FSTFieldValue.h
+++ b/Firestore/Source/Model/FSTFieldValue.h
@@ -22,7 +22,9 @@
@class FSTDocumentKey;
@class FSTFieldPath;
@class FSTTimestamp;
+@class FSTFieldValueOptions;
@class FIRGeoPoint;
+@class FIRSnapshotOptions;
NS_ASSUME_NONNULL_BEGIN
@@ -40,6 +42,32 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
FSTTypeOrderObject,
};
+/** Defines the return value for pending server timestamps. */
+typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) {
+ FSTServerTimestampBehaviorNone,
+ FSTServerTimestampBehaviorEstimate,
+ FSTServerTimestampBehaviorPrevious
+};
+
+/** Holds properties that define field value deserialization options. */
+@interface FSTFieldValueOptions : NSObject
+
+@property(nonatomic, readonly, assign) FSTServerTimestampBehavior serverTimestampBehavior;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Creates an FSTFieldValueOptions instance that specifies deserialization behavior for pending
+ * server timestamps.
+ */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior
+ NS_DESIGNATED_INITIALIZER;
+
+/** Creates an FSTFieldValueOption instance from FIRSnapshotOptions. */
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)value;
+
+@end
+
/**
* Abstract base class representing an immutable data value as stored in Firestore. FSTFieldValue
* represents all the different kinds of values that can be stored in fields in a document.
@@ -58,7 +86,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - Array
* - Object
*/
-@interface FSTFieldValue : NSObject
+@interface FSTFieldValue <__covariant T> : NSObject
/** Returns the FSTTypeOrder for this value. */
- (FSTTypeOrder)typeOrder;
@@ -69,7 +97,15 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* TODO(mikelehen): This conversion should probably happen at the API level and right now `value` is
* used inappropriately in the serializer implementation, etc. We need to do some reworking.
*/
-- (id)value;
+- (T)value;
+
+/**
+ * Converts an FSTFieldValue into the value that users will see in document snapshots.
+ *
+ * Options can be provided to configure the deserialization of some field values (such as server
+ * timestamps).
+ */
+- (T)valueWithOptions:(FSTFieldValueOptions *)options;
/** Compares against another FSTFieldValue. */
- (NSComparisonResult)compare:(FSTFieldValue *)other;
@@ -79,26 +115,24 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
/**
* A null value stored in Firestore. The |value| of a FSTNullValue is [NSNull null].
*/
-@interface FSTNullValue : FSTFieldValue
+@interface FSTNullValue : FSTFieldValue <NSNull *>
+ (instancetype)nullValue;
-- (id)value;
@end
/**
* A boolean value stored in Firestore.
*/
-@interface FSTBooleanValue : FSTFieldValue
+@interface FSTBooleanValue : FSTFieldValue <NSNumber *>
+ (instancetype)trueValue;
+ (instancetype)falseValue;
+ (instancetype)booleanValue:(BOOL)value;
-- (NSNumber *)value;
@end
/**
* Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number
* comparisons between the two types.
*/
-@interface FSTNumberValue : FSTFieldValue
+@interface FSTNumberValue : FSTFieldValue <NSNumber *>
@end
/**
@@ -106,7 +140,6 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
*/
@interface FSTIntegerValue : FSTNumberValue
+ (instancetype)integerValue:(int64_t)value;
-- (NSNumber *)value;
- (int64_t)internalValue;
@end
@@ -116,24 +149,21 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
@interface FSTDoubleValue : FSTNumberValue
+ (instancetype)doubleValue:(double)value;
+ (instancetype)nanValue;
-- (NSNumber *)value;
- (double)internalValue;
@end
/**
* A string stored in Firestore.
*/
-@interface FSTStringValue : FSTFieldValue
+@interface FSTStringValue : FSTFieldValue <NSString *>
+ (instancetype)stringValue:(NSString *)value;
-- (NSString *)value;
@end
/**
* A timestamp value stored in Firestore.
*/
-@interface FSTTimestampValue : FSTFieldValue
+@interface FSTTimestampValue : FSTFieldValue <NSDate *>
+ (instancetype)timestampValue:(FSTTimestamp *)value;
-- (NSDate *)value;
- (FSTTimestamp *)internalValue;
@end
@@ -144,46 +174,54 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - FSTServerTimestampValue instances are created as the result of applying an FSTTransformMutation
* (see [FSTTransformMutation applyTo]). They can only exist in the local view of a document.
* Therefore they do not need to be parsed or serialized.
- * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they evaluate to NSNull (at least
- * for now, see b/62064202).
+ * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they by default evaluate to NSNull.
+ * This behavior can be configured by passing custom FSTFieldValueOptions to `valueWithOptions:`.
* - They sort after all FSTTimestampValues. With respect to other FSTServerTimestampValues, they
* sort by their localWriteTime.
*/
-@interface FSTServerTimestampValue : FSTFieldValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime;
-- (NSNull *)value;
+@interface FSTServerTimestampValue : FSTFieldValue <id>
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue;
+
@property(nonatomic, strong, readonly) FSTTimestamp *localWriteTime;
+@property(nonatomic, strong, readonly, nullable) FSTFieldValue *previousValue;
+
@end
/**
* A geo point value stored in Firestore.
*/
-@interface FSTGeoPointValue : FSTFieldValue
+@interface FSTGeoPointValue : FSTFieldValue <FIRGeoPoint *>
+ (instancetype)geoPointValue:(FIRGeoPoint *)value;
-- (FIRGeoPoint *)value;
+- (FIRGeoPoint *)valueWithOptions:(FSTFieldValueOptions *)options;
@end
/**
* A blob value stored in Firestore.
*/
-@interface FSTBlobValue : FSTFieldValue
+@interface FSTBlobValue : FSTFieldValue <NSData *>
+ (instancetype)blobValue:(NSData *)value;
-- (NSData *)value;
+- (NSData *)valueWithOptions:(FSTFieldValueOptions *)options;
@end
/**
* A reference value stored in Firestore.
*/
-@interface FSTReferenceValue : FSTFieldValue
+@interface FSTReferenceValue : FSTFieldValue <FSTDocumentKey *>
+ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID;
-- (FSTDocumentKey *)value;
+- (FSTDocumentKey *)valueWithOptions:(FSTFieldValueOptions *)options;
@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
@end
/**
* A structured object value stored in Firestore.
*/
-@interface FSTObjectValue : FSTFieldValue
+// clang-format off
+@interface FSTObjectValue : FSTFieldValue < NSDictionary<NSString *, id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
+
/** Returns an empty FSTObjectValue. */
+ (instancetype)objectValue;
@@ -198,9 +236,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
- (instancetype)initWithImmutableDictionary:
(FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSDictionary<NSString *, id> *)value;
+- (NSDictionary<NSString *, id> *)valueWithOptions:(FSTFieldValueOptions *)options;
- (FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)internalValue;
/** Returns the value at the given path if it exists. Returns nil otherwise. */
@@ -222,19 +258,20 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
/**
* An array value stored in Firestore.
*/
-@interface FSTArrayValue : FSTFieldValue
+// clang-format off
+@interface FSTArrayValue : FSTFieldValue < NSArray <id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
/**
* Initializes this instance with the given array of wrapped values.
*
* @param value An immutable array of FSTFieldValue objects. Caller is responsible for copying the
- * value or releasing all references.
+ * value or releasing all references.
*/
- (instancetype)initWithValueNoCopy:(NSArray<FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSArray<id> *)value;
- (NSArray<FSTFieldValue *> *)internalValue;
@end
diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m
index 95ad306..a6326a7 100644
--- a/Firestore/Source/Model/FSTFieldValue.m
+++ b/Firestore/Source/Model/FSTFieldValue.m
@@ -17,6 +17,7 @@
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/API/FIRGeoPoint+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -27,6 +28,38 @@
NS_ASSUME_NONNULL_BEGIN
+#pragma mark - FSTFieldValueOptions
+
+@implementation FSTFieldValueOptions
+
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options {
+ if (options.serverTimestampBehavior == FSTServerTimestampBehaviorNone) {
+ static FSTFieldValueOptions *defaultInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ defaultInstance = [[FSTFieldValueOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
+ });
+ return defaultInstance;
+ } else {
+ return [[FSTFieldValueOptions alloc]
+ initWithServerTimestampBehavior:options.serverTimestampBehavior];
+ }
+}
+
+- (instancetype)initWithServerTimestampBehavior:
+ (FSTServerTimestampBehavior)serverTimestampBehavior {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ }
+ return self;
+}
+
+@end
+
#pragma mark - FSTFieldValue
@interface FSTFieldValue ()
@@ -40,6 +73,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (id)value {
+ return [self valueWithOptions:[FSTFieldValueOptions
+ optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]];
+}
+
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
@throw FSTAbstractMethodException(); // NOLINT
}
@@ -89,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderNull;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return [NSNull null];
}
@@ -155,7 +193,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderBoolean;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue ? @YES : @NO;
}
@@ -233,7 +271,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return @(self.internalValue);
}
@@ -285,7 +323,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return @(self.internalValue);
}
@@ -332,7 +370,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderString;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -379,7 +417,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderTimestamp;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
// For developers, we expose Timestamps as Dates.
return self.internalValue.approximateDateValue;
}
@@ -410,14 +448,18 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTServerTimestampValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
- return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime];
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
+ return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime
+ previousValue:previousValue];
}
-- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
+- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
self = [super init];
if (self) {
_localWriteTime = localWriteTime;
+ _previousValue = previousValue;
}
return self;
}
@@ -426,9 +468,17 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderTimestamp;
}
-- (NSNull *)value {
- // For developers, server timestamps always evaluate to NSNull (for now, at least; b/62064202).
- return [NSNull null];
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ switch (options.serverTimestampBehavior) {
+ case FSTServerTimestampBehaviorNone:
+ return [NSNull null];
+ case FSTServerTimestampBehaviorEstimate:
+ return [self.localWriteTime approximateDateValue];
+ case FSTServerTimestampBehaviorPrevious:
+ return self.previousValue ? [self.previousValue valueWithOptions:options] : [NSNull null];
+ default:
+ FSTFail(@"Unexpected server timestamp option: %d", (int)options.serverTimestampBehavior);
+ }
}
- (BOOL)isEqual:(id)other {
@@ -481,7 +531,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderGeoPoint;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -529,7 +579,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderBlob;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -573,7 +623,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.key;
}
@@ -648,11 +698,11 @@ NS_ASSUME_NONNULL_BEGIN
return [self initWithImmutableDictionary:dictionary];
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[self.internalValue
enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *obj, BOOL *stop) {
- result[key] = [obj value];
+ result[key] = [obj valueWithOptions:options];
}];
return result;
}
@@ -803,7 +853,7 @@ NS_ASSUME_NONNULL_BEGIN
return [self.internalValue hash];
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:_internalValue.count];
[self.internalValue enumerateObjectsUsingBlock:^(FSTFieldValue *obj, NSUInteger idx, BOOL *stop) {
[result addObject:[obj value]];
diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h
index ef7f1c8..7c5f6de 100644
--- a/Firestore/Source/Model/FSTMutation.h
+++ b/Firestore/Source/Model/FSTMutation.h
@@ -158,8 +158,10 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil, if we don't have
* information about this document. Both the input and returned documents can be nil.
*
- * @param maybeDoc The document to mutate. The input document should nil if it does not currently
- * exist.
+ * @param maybeDoc The current state of the document to mutate. The input document should be nil if
+ * it does not currently exist.
+ * @param baseDoc The state of the document prior to this mutation batch. The input document should
+ * be nil if it the document did not exist.
* @param localWriteTime A timestamp indicating the local write time of the batch this mutation is
* a part of.
* @param mutationResult Optional result info from the backend. If omitted, it's assumed that
@@ -196,16 +198,18 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* apply the transform if the prior mutation resulted in an FSTDocument (always true for an
* FSTSetMutation, but not necessarily for an FSTPatchMutation).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult;
+ mutationResult:(nullable FSTMutationResult *)mutationResult;
/**
* A helper version of applyTo for applying mutations locally (without a mutation result from the
* backend).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime;
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FSTTimestamp *)localWriteTime;
@property(nonatomic, strong, readonly) FSTDocumentKey *key;
diff --git a/Firestore/Source/Model/FSTMutation.m b/Firestore/Source/Model/FSTMutation.m
index 5b47280..c249138 100644
--- a/Firestore/Source/Model/FSTMutation.m
+++ b/Firestore/Source/Model/FSTMutation.m
@@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
FSTFieldTransform *otherFieldTransform = other;
return [self.path isEqual:otherFieldTransform.path] &&
[self.transform isEqual:otherFieldTransform.transform];
@@ -236,15 +236,18 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
@throw FSTAbstractMethodException(); // NOLINT
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime {
- return [self applyTo:maybeDoc localWriteTime:localWriteTime mutationResult:nil];
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FSTTimestamp *)localWriteTime {
+ return
+ [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
}
@end
@@ -287,9 +290,10 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
}
@@ -362,9 +366,10 @@ NS_ASSUME_NONNULL_BEGIN
self.key, self.fieldMask, self.value, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
}
@@ -451,9 +456,10 @@ NS_ASSUME_NONNULL_BEGIN
self.key, self.fieldTransforms, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(mutationResult.transformResults,
@"Transform results missing for FSTTransformMutation.");
@@ -473,8 +479,9 @@ NS_ASSUME_NONNULL_BEGIN
BOOL hasLocalMutations = (mutationResult == nil);
NSArray<FSTFieldValue *> *transformResults =
- mutationResult ? mutationResult.transformResults
- : [self localTransformResultsWithWriteTime:localWriteTime];
+ mutationResult
+ ? mutationResult.transformResults
+ : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
return [FSTDocument documentWithData:newData
key:doc.key
@@ -486,16 +493,26 @@ NS_ASSUME_NONNULL_BEGIN
* Creates an array of "transform results" (a transform result is a field value representing the
* result of applying a transform) for use when applying an FSTTransformMutation locally.
*
+ * @param baseDocument The document prior to applying this mutation batch.
* @param localWriteTime The local time of the transform mutation (used to generate
* FSTServerTimestampValues).
* @return The transform results array.
*/
-- (NSArray<FSTFieldValue *> *)localTransformResultsWithWriteTime:(FSTTimestamp *)localWriteTime {
+- (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
+ (FSTMaybeDocument *_Nullable)baseDocument
+ writeTime:(FSTTimestamp *)localWriteTime {
NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
for (FSTFieldTransform *fieldTransform in self.fieldTransforms) {
if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) {
- [transformResults addObject:[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:localWriteTime]];
+ FSTFieldValue *previousValue = nil;
+
+ if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
+ previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path];
+ }
+
+ [transformResults
+ addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
+ previousValue:previousValue]];
} else {
FSTFail(@"Encountered unknown transform: %@", fieldTransform);
}
@@ -551,9 +568,10 @@ NS_ASSUME_NONNULL_BEGIN
stringWithFormat:@"<FSTDeleteMutation key=%@ precondition=%@>", self.key, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults,
@"Transform results received by FSTDeleteMutation.");
diff --git a/Firestore/Source/Model/FSTMutationBatch.m b/Firestore/Source/Model/FSTMutationBatch.m
index 3677908..01adca7 100644
--- a/Firestore/Source/Model/FSTMutationBatch.m
+++ b/Firestore/Source/Model/FSTMutationBatch.m
@@ -71,6 +71,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
mutationBatchResult:(FSTMutationBatchResult *_Nullable)mutationBatchResult {
FSTAssert(!maybeDoc || [maybeDoc.key isEqualToKey:documentKey],
@"applyTo: key %@ doesn't match maybeDoc key %@", documentKey, maybeDoc.key);
+ FSTMaybeDocument *baseDoc = maybeDoc;
if (mutationBatchResult) {
FSTAssert(mutationBatchResult.mutationResults.count == self.mutations.count,
@"Mismatch between mutations length (%lu) and results length (%lu)",
@@ -83,6 +84,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
FSTMutationResult *_Nullable mutationResult = mutationBatchResult.mutationResults[i];
if ([mutation.key isEqualToKey:documentKey]) {
maybeDoc = [mutation applyTo:maybeDoc
+ baseDocument:baseDoc
localWriteTime:self.localWriteTime
mutationResult:mutationResult];
}
diff --git a/Firestore/Source/Model/FSTPath.m b/Firestore/Source/Model/FSTPath.m
index b236107..636c322 100644
--- a/Firestore/Source/Model/FSTPath.m
+++ b/Firestore/Source/Model/FSTPath.m
@@ -240,7 +240,7 @@ NS_ASSUME_NONNULL_BEGIN
c = *source++;
// TODO(b/37244157): Make this a user-facing exception once we finalize field escaping.
FSTAssert(c != '\0', @"Trailing escape characters not allowed in %@", fieldPath);
- // Fall through
+ // Fall through
default:
// copy into the current segment
diff --git a/Firestore/Source/Public/FIRDocumentChange.h b/Firestore/Source/Public/FIRDocumentChange.h
index 022c81b..4717067 100644
--- a/Firestore/Source/Public/FIRDocumentChange.h
+++ b/Firestore/Source/Public/FIRDocumentChange.h
@@ -18,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
-@class FIRDocumentSnapshot;
+@class FIRQueryDocumentSnapshot;
/** An enumeration of document change types. */
typedef NS_ENUM(NSInteger, FIRDocumentChangeType) {
@@ -47,7 +47,7 @@ NS_SWIFT_NAME(DocumentChange)
@property(nonatomic, readonly) FIRDocumentChangeType type;
/** The document affected by this change. */
-@property(nonatomic, strong, readonly) FIRDocumentSnapshot *document;
+@property(nonatomic, strong, readonly) FIRQueryDocumentSnapshot *document;
/**
* The index of the changed document in the result set immediately prior to this FIRDocumentChange
diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h
index 439e727..7fcc7a8 100644
--- a/Firestore/Source/Public/FIRDocumentReference.h
+++ b/Firestore/Source/Public/FIRDocumentReference.h
@@ -36,8 +36,6 @@ NS_SWIFT_NAME(DocumentListenOptions)
- (instancetype)init;
-@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
-
/**
* Sets the includeMetadataChanges option which controls whether metadata-only changes (i.e. only
* `FIRDocumentSnapshot.metadata` changed) should trigger snapshot events. Default is NO.
diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h
index 3e67c25..6e79a7f 100644
--- a/Firestore/Source/Public/FIRDocumentSnapshot.h
+++ b/Firestore/Source/Public/FIRDocumentSnapshot.h
@@ -22,9 +22,61 @@
NS_ASSUME_NONNULL_BEGIN
/**
+ * Controls the return value for server timestamps that have not yet been set to
+ * their final value.
+ */
+typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) {
+ /**
+ * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet
+ * been set to their final value.
+ */
+ FIRServerTimestampBehaviorNone,
+
+ /**
+ * Return a local estimates for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value. This estimate will
+ * likely differ from the final value and may cause these pending values to
+ * change once the server result becomes available.
+ */
+ FIRServerTimestampBehaviorEstimate,
+
+ /**
+ * Return the previous value for `FieldValue.serverTimestamp()` fields that
+ * have not yet been set to their final value.
+ */
+ FIRServerTimestampBehaviorPrevious
+} NS_SWIFT_NAME(ServerTimestampBehavior);
+
+/**
+ * Options that configure how data is retrieved from a `DocumentSnapshot`
+ * (e.g. the desired behavior for server timestamps that have not yet been set
+ * to their final value).
+ */
+NS_SWIFT_NAME(SnapshotOptions)
+@interface FIRSnapshotOptions : NSObject
+
+/** */
+- (instancetype)init __attribute__((unavailable("FIRSnapshotOptions cannot be created directly.")));
+
+/**
+ * If set, controls the return value for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value.
+ *
+ * If omitted, `NSNull` will be returned by default.
+ *
+ * @return The created `FIRSnapshotOptions` object.
+ */
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior;
+
+@end
+
+/**
* A `FIRDocumentSnapshot` contains data read from a document in your Firestore database. The data
* can be extracted with the `data` property or by using subscript syntax to access a specific
* field.
+ *
+ * For a `FIRDocumentSnapshot` that points to a non-existing document, any data access will return
+ * `nil`. You can use the `exists` property to explicitly verify a documents existence.
*/
NS_SWIFT_NAME(DocumentSnapshot)
@interface FIRDocumentSnapshot : NSObject
@@ -46,21 +98,104 @@ NS_SWIFT_NAME(DocumentSnapshot)
@property(nonatomic, strong, readonly) FIRSnapshotMetadata *metadata;
/**
- * Retrieves all fields in the document as an `NSDictionary`.
+ * Retrieves all fields in the document as an `NSDictionary`. Returns `nil` if the document doesn't
+ * exist.
*
- * @return An `NSDictionary` containing all fields in the document.
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
*/
-- (NSDictionary<NSString *, id> *)data;
+- (nullable NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`. Returns `nil` if the document doesn't
+ * exist.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
+ */
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+- (nullable id)valueForField:(id)field NS_SWIFT_NAME(get(_:));
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+// clang-format off
+- (nullable id)valueForField:(id)field
+ options:(FIRSnapshotOptions *)options
+ NS_SWIFT_NAME(get(_:options:));
+// clang-format on
/**
* Retrieves a specific field from the document.
*
* @param key The field to retrieve.
*
- * @return The value contained in the field or `nil` if the field doesn't exist.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
*/
- (nullable id)objectForKeyedSubscript:(id)key;
@end
+/**
+ * A `FIRQueryDocumentSnapshot` contains data read from a document in your Firestore database as
+ * part of a query. The document is guaranteed to exist and its data can be extracted with the
+ * `data` property or by using subscript syntax to access a specific field.
+ *
+ * A `FIRQueryDocumentSnapshot` offers the same API surface as a `FIRDocumentSnapshot`. As
+ * deleted documents are not returned from queries, its `exists` property will always be true and
+ * `data:` will never return `nil`.
+ */
+NS_SWIFT_NAME(QueryDocumentSnapshot)
+@interface FIRQueryDocumentSnapshot : FIRDocumentSnapshot
+
+/** */
+- (instancetype)init
+ __attribute__((unavailable("FIRQueryDocumentSnapshot cannot be created directly.")));
+
+/**
+ * Retrieves all fields in the document as an `NSDictionary`.
+ *
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRFirestore.h b/Firestore/Source/Public/FIRFirestore.h
index 91a96a5..4c85aba 100644
--- a/Firestore/Source/Public/FIRFirestore.h
+++ b/Firestore/Source/Public/FIRFirestore.h
@@ -139,6 +139,23 @@ NS_SWIFT_NAME(Firestore)
+ (void)enableLogging:(BOOL)logging
DEPRECATED_MSG_ATTRIBUTE("Use FIRSetLoggerLevel(FIRLoggerLevelDebug) to enable logging");
+#pragma mark - Network
+
+/**
+ * Re-enables usage of the network by this Firestore instance after a prior call to
+ * `disableNetworkWithCompletion`. Completion block, if provided, will be called once network uasge
+ * has been enabled.
+ */
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
+/**
+ * Disables usage of the network by this Firestore instance. It can be re-enabled by via
+ * `enableNetworkWithCompletion`. While the network is disabled, any snapshot listeners or get calls
+ * will return results from cache and any write operations will be queued until the network is
+ * restored. The completion block, if provided, will be called once network usage has been disabled.
+ */
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRQuery.h b/Firestore/Source/Public/FIRQuery.h
index 0f3aeed..ff15ac6 100644
--- a/Firestore/Source/Public/FIRQuery.h
+++ b/Firestore/Source/Public/FIRQuery.h
@@ -256,6 +256,19 @@ NS_SWIFT_NAME(Query)
isGreaterThanOrEqualTo:(id)value NS_SWIFT_NAME(whereField(_:isGreaterThanOrEqualTo:));
// clang-format on
+/**
+ * Creates and returns a new `FIRQuery` with the additional filter that documents must
+ * satisfy the specified predicate.
+ *
+ * @param predicate The predicate the document must satisfy. Can be either comparison
+ * or compound of comparison. In particular, block-based predicate is not supported.
+ *
+ * @return The created `FIRQuery`.
+ */
+// clang-format off
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate NS_SWIFT_NAME(filter(using:));
+// clang-format on
+
#pragma mark - Sorting Data
/**
* Creates and returns a new `FIRQuery` that's additionally sorted by the specified field.
diff --git a/Firestore/Source/Public/FIRQuerySnapshot.h b/Firestore/Source/Public/FIRQuerySnapshot.h
index c49a07a..1266832 100644
--- a/Firestore/Source/Public/FIRQuerySnapshot.h
+++ b/Firestore/Source/Public/FIRQuerySnapshot.h
@@ -19,8 +19,8 @@
NS_ASSUME_NONNULL_BEGIN
@class FIRDocumentChange;
-@class FIRDocumentSnapshot;
@class FIRQuery;
+@class FIRQueryDocumentSnapshot;
@class FIRSnapshotMetadata;
/**
@@ -50,7 +50,7 @@ NS_SWIFT_NAME(QuerySnapshot)
@property(nonatomic, readonly) NSInteger count;
/** An Array of the `FIRDocumentSnapshots` that make up this document set. */
-@property(nonatomic, strong, readonly) NSArray<FIRDocumentSnapshot *> *documents;
+@property(nonatomic, strong, readonly) NSArray<FIRQueryDocumentSnapshot *> *documents;
/**
* An array of the documents that changed since the last snapshot. If this is the first snapshot,
diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h
index 5f0034c..8ff1bec 100644
--- a/Firestore/Source/Public/FIRWriteBatch.h
+++ b/Firestore/Source/Public/FIRWriteBatch.h
@@ -94,6 +94,11 @@ NS_SWIFT_NAME(WriteBatch)
/**
* Commits all of the writes in this write batch as a single atomic unit.
+ */
+- (void)commit;
+
+/**
+ * Commits all of the writes in this write batch as a single atomic unit.
*
* @param completion A block to be called once all of the writes in the batch have been
* successfully written to the backend as an atomic unit. This block will only execute
@@ -101,7 +106,7 @@ NS_SWIFT_NAME(WriteBatch)
* completion handler will not be called when the device is offline, though local
* changes will be visible immediately.
*/
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion;
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h
index 313ddb7..18331ff 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.h
+++ b/Firestore/Source/Remote/FSTRemoteStore.h
@@ -83,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol FSTOnlineStateDelegate <NSObject>
/** Called whenever the online state of the watch stream changes */
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.m b/Firestore/Source/Remote/FSTRemoteStore.m
index 063e487..a0c5059 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.m
+++ b/Firestore/Source/Remote/FSTRemoteStore.m
@@ -160,27 +160,38 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self enableNetwork];
}
-- (void)setOnlineStateToHealthy {
- self.shouldWarnOffline = NO;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateHealthy];
-}
-
-- (void)setOnlineStateToUnknown {
- // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout) or
- // when we have no active listens and therefore there's no need to start the stream. Assuming
- // there is (possibly in the future) an active listen, then we will eventually move to state
- // Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure attempts
- // before failing, so we reset the count here.
- self.watchStreamFailures = 0;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateUnknown];
+/**
+ * Updates our OnlineState to the new state, updating local state and notifying the
+ * onlineStateHandler as appropriate.
+ */
+- (void)updateOnlineState:(FSTOnlineState)newState {
+ // Update and broadcast the new state.
+ if (newState != self.watchStreamOnlineState) {
+ if (newState == FSTOnlineStateHealthy) {
+ // We've connected to watch at least once. Don't warn the developer about being offline going
+ // forward.
+ self.shouldWarnOffline = NO;
+ } else if (newState == FSTOnlineStateUnknown) {
+ // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout)
+ // or when we have no active listens and therefore there's no need to start the stream.
+ // Assuming there is (possibly in the future) an active listen, then we will eventually move
+ // to state Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure
+ // attempts before failing, so we reset the count here.
+ self.watchStreamFailures = 0;
+ }
+ self.watchStreamOnlineState = newState;
+ [self.onlineStateDelegate applyChangedOnlineState:newState];
+ }
}
+/**
+ * Updates our FSTOnlineState as appropriate after the watch stream reports a failure. The first
+ * failure moves us to the 'Unknown' state. We then may allow multiple failures (based on
+ * kOnlineAttemptsBeforeFailure) before we actually transition to FSTOnlineStateFailed.
+ */
- (void)updateOnlineStateAfterFailure {
- // The first failure after we are successfully connected moves us to the 'Unknown' state. We
- // then may make multiple attempts (based on kOnlineAttemptsBeforeFailure) before we actually
- // report failure.
if (self.watchStreamOnlineState == FSTOnlineStateHealthy) {
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
} else {
self.watchStreamFailures++;
if (self.watchStreamFailures >= kOnlineAttemptsBeforeFailure) {
@@ -188,19 +199,11 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTWarn(@"Could not reach Firestore backend.");
self.shouldWarnOffline = NO;
}
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
+ [self updateOnlineState:FSTOnlineStateFailed];
}
}
}
-- (void)updateAndNotifyAboutOnlineState:(FSTOnlineState)watchStreamOnlineState {
- BOOL didChange = (watchStreamOnlineState != self.watchStreamOnlineState);
- self.watchStreamOnlineState = watchStreamOnlineState;
- if (didChange) {
- [self.onlineStateDelegate watchStreamDidChangeOnlineState:watchStreamOnlineState];
- }
-}
-
#pragma mark Online/Offline state
- (BOOL)isNetworkEnabled {
@@ -210,8 +213,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
}
- (void)enableNetwork {
- FSTAssert(self.watchStream == nil, @"enableNetwork: called with non-null watchStream.");
- FSTAssert(self.writeStream == nil, @"enableNetwork: called with non-null writeStream.");
+ if ([self isNetworkEnabled]) {
+ return;
+ }
// Create new streams (but note they're not started yet).
self.watchStream = [self.datastore createWatchStream];
@@ -227,47 +231,51 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self fillWritePipeline]; // This may start the writeStream.
// We move back to the unknown state because we might not want to re-open the stream
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
- (void)disableNetwork {
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to failed so get()'s return from cache, etc.
+ [self updateOnlineState:FSTOnlineStateFailed];
+}
- // NOTE: We're guaranteed not to get any further events from these streams (not even a close
- // event).
- [self.watchStream stop];
- [self.writeStream stop];
+/** Disables the network, setting the FSTOnlineState to the specified targetOnlineState. */
+- (void)disableNetworkInternal {
+ if ([self isNetworkEnabled]) {
+ // NOTE: We're guaranteed not to get any further events from these streams (not even a close
+ // event).
+ [self.watchStream stop];
+ [self.writeStream stop];
- [self cleanUpWatchStreamState];
- [self cleanUpWriteStreamState];
+ [self cleanUpWatchStreamState];
+ [self cleanUpWriteStreamState];
- self.writeStream = nil;
- self.watchStream = nil;
+ self.writeStream = nil;
+ self.watchStream = nil;
+ }
}
#pragma mark Shutdown
- (void)shutdown {
FSTLog(@"FSTRemoteStore %p shutting down", (__bridge void *)self);
-
- // Don't fire initial listener callbacks on shutdown.
- self.onlineStateDelegate = nil;
-
- // For now, all shutdown logic is handled by disableNetwork(). We might expand on this in the
- // future.
- if ([self isNetworkEnabled]) {
- [self disableNetwork];
- }
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to Unknown (rather than Failed) to avoid potentially triggering
+ // spurious listener events with cached data, etc.
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
- (void)userDidChange:(FSTUser *)user {
FSTLog(@"FSTRemoteStore %p changing users: %@", (__bridge void *)self, user);
-
- // Tear down and re-create our network streams. This will ensure we get a fresh auth token
- // for the new user and re-fill the write pipeline with new mutations from the LocalStore
- // (since mutations are per-user).
- [self disableNetwork];
- [self enableNetwork];
+ if ([self isNetworkEnabled]) {
+ // Tear down and re-create our network streams. This will ensure we get a fresh auth token
+ // for the new user and re-fill the write pipeline with new mutations from the LocalStore
+ // (since mutations are per-user).
+ [self disableNetworkInternal];
+ [self updateOnlineState:FSTOnlineStateUnknown];
+ [self enableNetwork];
+ }
}
#pragma mark Watch Stream
@@ -348,7 +356,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamDidChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
// Mark the connection as healthy because we got a message from the server.
- [self setOnlineStateToHealthy];
+ [self updateOnlineState:FSTOnlineStateHealthy];
FSTWatchTargetChange *watchTargetChange =
[change isKindOfClass:[FSTWatchTargetChange class]] ? (FSTWatchTargetChange *)change : nil;
@@ -391,7 +399,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
} else {
// We don't need to restart the watch stream because there are no active targets. The online
// state is set to unknown because there is no active attempt at establishing a connection.
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
}
@@ -532,6 +540,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)cleanUpWriteStreamState {
self.lastBatchSeen = kFSTBatchIDUnknown;
+ FSTLog(@"Stopping write stream with %lu pending writes",
+ (unsigned long)[self.pendingWrites count]);
[self.pendingWrites removeAllObjects];
}
diff --git a/Firestore/Source/Remote/FSTStream.m b/Firestore/Source/Remote/FSTStream.m
index 2c039be..5719ec8 100644
--- a/Firestore/Source/Remote/FSTStream.m
+++ b/Firestore/Source/Remote/FSTStream.m
@@ -542,7 +542,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
FSTWeakify(self);
[self.workerDispatchQueue dispatchAsync:^{
FSTStrongify(self);
- if (!self || ![self isStarted]) {
+ if (![self isStarted]) {
FSTLog(@"%@ Ignoring stream message from inactive stream.", NSStringFromClass([self class]));
}