mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
* Move all Firestore files to Objective-C++
* Update project file references
* Don't use module imports from Objective-C++
* Use extern "C" for C-accessible globals
* Work around more stringent type checking in Objective-C++
* NSMutableDictionary ivars aren't implicitly casted to NSDictionary
* FSTMaybeDocument callback can't be passed a function that accepts
FSTDocument
* NSComparisonResult can't be multiplied by -1 without casting
* Add a #include <inttypes.h> where needed
* Avoid using C++ keywords as variables
* Remove #if __cplusplus guards
299 lines
12 KiB
Plaintext
299 lines
12 KiB
Plaintext
/*
|
|
* 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 "Firestore/Source/Core/FSTFirestoreClient.h"
|
|
|
|
#import "Firestore/Source/Auth/FSTCredentialsProvider.h"
|
|
#import "Firestore/Source/Core/FSTDatabaseInfo.h"
|
|
#import "Firestore/Source/Core/FSTEventManager.h"
|
|
#import "Firestore/Source/Core/FSTSyncEngine.h"
|
|
#import "Firestore/Source/Core/FSTTransaction.h"
|
|
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
|
|
#import "Firestore/Source/Local/FSTLevelDB.h"
|
|
#import "Firestore/Source/Local/FSTLocalSerializer.h"
|
|
#import "Firestore/Source/Local/FSTLocalStore.h"
|
|
#import "Firestore/Source/Local/FSTMemoryPersistence.h"
|
|
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
|
|
#import "Firestore/Source/Remote/FSTDatastore.h"
|
|
#import "Firestore/Source/Remote/FSTRemoteStore.h"
|
|
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
|
|
#import "Firestore/Source/Util/FSTAssert.h"
|
|
#import "Firestore/Source/Util/FSTClasses.h"
|
|
#import "Firestore/Source/Util/FSTDispatchQueue.h"
|
|
#import "Firestore/Source/Util/FSTLogger.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@interface FSTFirestoreClient ()
|
|
- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
|
|
usePersistence:(BOOL)usePersistence
|
|
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
|
|
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
|
|
workerDispatchQueue:(FSTDispatchQueue *)queue NS_DESIGNATED_INITIALIZER;
|
|
|
|
@property(nonatomic, strong, readonly) FSTDatabaseInfo *databaseInfo;
|
|
@property(nonatomic, strong, readonly) FSTEventManager *eventManager;
|
|
@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
|
|
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
|
|
@property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
|
|
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
|
|
|
|
/**
|
|
* Dispatch queue responsible for all of our internal processing. When we get incoming work from
|
|
* the user (via public API) or the network (incoming GRPC messages), we should always dispatch
|
|
* onto this queue. This ensures our internal data structures are never accessed from multiple
|
|
* threads simultaneously.
|
|
*/
|
|
@property(nonatomic, strong, readonly) FSTDispatchQueue *workerDispatchQueue;
|
|
|
|
@property(nonatomic, strong, readonly) id<FSTCredentialsProvider> credentialsProvider;
|
|
|
|
@end
|
|
|
|
@implementation FSTFirestoreClient
|
|
|
|
+ (instancetype)clientWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
|
|
usePersistence:(BOOL)usePersistence
|
|
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
|
|
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
|
|
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
|
|
return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo
|
|
usePersistence:usePersistence
|
|
credentialsProvider:credentialsProvider
|
|
userDispatchQueue:userDispatchQueue
|
|
workerDispatchQueue:workerDispatchQueue];
|
|
}
|
|
|
|
- (instancetype)initWithDatabaseInfo:(FSTDatabaseInfo *)databaseInfo
|
|
usePersistence:(BOOL)usePersistence
|
|
credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
|
|
userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
|
|
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
|
|
if (self = [super init]) {
|
|
_databaseInfo = databaseInfo;
|
|
_credentialsProvider = credentialsProvider;
|
|
_userDispatchQueue = userDispatchQueue;
|
|
_workerDispatchQueue = workerDispatchQueue;
|
|
|
|
dispatch_semaphore_t initialUserAvailable = dispatch_semaphore_create(0);
|
|
__block FSTUser *initialUser;
|
|
FSTWeakify(self);
|
|
_credentialsProvider.userChangeListener = ^(FSTUser *user) {
|
|
FSTStrongify(self);
|
|
if (self) {
|
|
if (!initialUser) {
|
|
initialUser = user;
|
|
dispatch_semaphore_signal(initialUserAvailable);
|
|
} else {
|
|
[workerDispatchQueue dispatchAsync:^{
|
|
[self userDidChange:user];
|
|
}];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Defer initialization until we get the current user from the userChangeListener. This is
|
|
// guaranteed to be synchronously dispatched onto our worker queue, so we will be initialized
|
|
// before any subsequently queued work runs.
|
|
[_workerDispatchQueue dispatchAsync:^{
|
|
dispatch_semaphore_wait(initialUserAvailable, DISPATCH_TIME_FOREVER);
|
|
|
|
[self initializeWithUser:initialUser usePersistence:usePersistence];
|
|
}];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)initializeWithUser:(FSTUser *)user usePersistence:(BOOL)usePersistence {
|
|
// Do all of our initialization on our own dispatch queue.
|
|
[self.workerDispatchQueue verifyIsCurrentQueue];
|
|
|
|
// Note: The initialization work must all be synchronous (we can't dispatch more work) since
|
|
// external write/listen operations could get queued to run before that subsequent work
|
|
// completes.
|
|
id<FSTGarbageCollector> garbageCollector;
|
|
if (usePersistence) {
|
|
// TODO(http://b/33384523): For now we just disable garbage collection when persistence is
|
|
// enabled.
|
|
garbageCollector = [[FSTNoOpGarbageCollector alloc] init];
|
|
|
|
NSString *dir = [FSTLevelDB storageDirectoryForDatabaseInfo:self.databaseInfo
|
|
documentsDirectory:[FSTLevelDB documentsDirectory]];
|
|
|
|
FSTSerializerBeta *remoteSerializer =
|
|
[[FSTSerializerBeta alloc] initWithDatabaseID:self.databaseInfo.databaseID];
|
|
FSTLocalSerializer *serializer =
|
|
[[FSTLocalSerializer alloc] initWithRemoteSerializer:remoteSerializer];
|
|
|
|
_persistence = [[FSTLevelDB alloc] initWithDirectory:dir serializer:serializer];
|
|
} else {
|
|
garbageCollector = [[FSTEagerGarbageCollector alloc] init];
|
|
_persistence = [FSTMemoryPersistence persistence];
|
|
}
|
|
|
|
NSError *error;
|
|
if (![_persistence start:&error]) {
|
|
// If local storage fails to start then just throw up our hands: the error is unrecoverable.
|
|
// There's nothing an end-user can do and nearly all failures indicate the developer is doing
|
|
// something grossly wrong so we should stop them cold in their tracks with a failure they
|
|
// can't ignore.
|
|
[NSException raise:NSInternalInconsistencyException format:@"Failed to open DB: %@", error];
|
|
}
|
|
|
|
_localStore = [[FSTLocalStore alloc] initWithPersistence:_persistence
|
|
garbageCollector:garbageCollector
|
|
initialUser:user];
|
|
|
|
FSTDatastore *datastore = [FSTDatastore datastoreWithDatabase:self.databaseInfo
|
|
workerDispatchQueue:self.workerDispatchQueue
|
|
credentials:self.credentialsProvider];
|
|
|
|
_remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:datastore];
|
|
|
|
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
|
|
remoteStore:_remoteStore
|
|
initialUser:user];
|
|
|
|
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
|
|
|
|
// Setup wiring for remote store.
|
|
_remoteStore.syncEngine = _syncEngine;
|
|
|
|
_remoteStore.onlineStateDelegate = self;
|
|
|
|
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
|
|
// queue, etc.) so must be started after LocalStore.
|
|
[_localStore start];
|
|
[_remoteStore start];
|
|
}
|
|
|
|
- (void)userDidChange:(FSTUser *)user {
|
|
[self.workerDispatchQueue verifyIsCurrentQueue];
|
|
|
|
FSTLog(@"User Changed: %@", user);
|
|
[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];
|
|
if (completion) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(nil);
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)enableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
[self.remoteStore enableNetwork];
|
|
if (completion) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(nil);
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)shutdownWithCompletion:(nullable FSTVoidErrorBlock)completion {
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
self.credentialsProvider.userChangeListener = nil;
|
|
|
|
[self.remoteStore shutdown];
|
|
[self.localStore shutdown];
|
|
[self.persistence shutdown];
|
|
if (completion) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(nil);
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (FSTQueryListener *)listenToQuery:(FSTQuery *)query
|
|
options:(FSTListenOptions *)options
|
|
viewSnapshotHandler:(FSTViewSnapshotHandler)viewSnapshotHandler {
|
|
FSTQueryListener *listener = [[FSTQueryListener alloc] initWithQuery:query
|
|
options:options
|
|
viewSnapshotHandler:viewSnapshotHandler];
|
|
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
[self.eventManager addListener:listener];
|
|
}];
|
|
|
|
return listener;
|
|
}
|
|
|
|
- (void)removeListener:(FSTQueryListener *)listener {
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
[self.eventManager removeListener:listener];
|
|
}];
|
|
}
|
|
|
|
- (void)writeMutations:(NSArray<FSTMutation *> *)mutations
|
|
completion:(nullable FSTVoidErrorBlock)completion {
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
if (mutations.count == 0) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(nil);
|
|
}];
|
|
} else {
|
|
[self.syncEngine writeMutations:mutations
|
|
completion:^(NSError *error) {
|
|
// Dispatch the result back onto the user dispatch queue.
|
|
if (completion) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(error);
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
}];
|
|
};
|
|
|
|
- (void)transactionWithRetries:(int)retries
|
|
updateBlock:(FSTTransactionBlock)updateBlock
|
|
completion:(FSTVoidIDErrorBlock)completion {
|
|
[self.workerDispatchQueue dispatchAsync:^{
|
|
[self.syncEngine transactionWithRetries:retries
|
|
workerDispatchQueue:self.workerDispatchQueue
|
|
updateBlock:updateBlock
|
|
completion:^(id _Nullable result, NSError *_Nullable error) {
|
|
// Dispatch the result back onto the user dispatch queue.
|
|
if (completion) {
|
|
[self.userDispatchQueue dispatchAsync:^{
|
|
completion(result, error);
|
|
}];
|
|
}
|
|
}];
|
|
|
|
}];
|
|
}
|
|
|
|
- (FSTDatabaseID *)databaseID {
|
|
return self.databaseInfo.databaseID;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|