FIRFirestore.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FIRFirestore.h"
  17. #import <FirebaseCore/FIRApp.h>
  18. #import <FirebaseCore/FIRLogger.h>
  19. #import <FirebaseCore/FIROptions.h>
  20. #import "FIRFirestoreSettings.h"
  21. #import "Firestore/Source/API/FIRCollectionReference+Internal.h"
  22. #import "Firestore/Source/API/FIRDocumentReference+Internal.h"
  23. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  24. #import "Firestore/Source/API/FIRTransaction+Internal.h"
  25. #import "Firestore/Source/API/FIRWriteBatch+Internal.h"
  26. #import "Firestore/Source/API/FSTUserDataConverter.h"
  27. #import "Firestore/Source/Auth/FSTCredentialsProvider.h"
  28. #import "Firestore/Source/Core/FSTDatabaseInfo.h"
  29. #import "Firestore/Source/Core/FSTFirestoreClient.h"
  30. #import "Firestore/Source/Model/FSTDatabaseID.h"
  31. #import "Firestore/Source/Model/FSTDocumentKey.h"
  32. #import "Firestore/Source/Model/FSTPath.h"
  33. #import "Firestore/Source/Util/FSTAssert.h"
  34. #import "Firestore/Source/Util/FSTDispatchQueue.h"
  35. #import "Firestore/Source/Util/FSTLogger.h"
  36. #import "Firestore/Source/Util/FSTUsageValidation.h"
  37. NS_ASSUME_NONNULL_BEGIN
  38. NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
  39. @interface FIRFirestore ()
  40. @property(nonatomic, strong) FSTDatabaseID *databaseID;
  41. @property(nonatomic, strong) NSString *persistenceKey;
  42. @property(nonatomic, strong) id<FSTCredentialsProvider> credentialsProvider;
  43. @property(nonatomic, strong) FSTDispatchQueue *workerDispatchQueue;
  44. // Note that `client` is updated after initialization, but marking this readwrite would generate an
  45. // incorrect setter (since we make the assignment to `client` inside an `@synchronized` block.
  46. @property(nonatomic, strong, readonly) FSTFirestoreClient *client;
  47. @property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
  48. @end
  49. @implementation FIRFirestore {
  50. // All guarded by @synchronized(self)
  51. FIRFirestoreSettings *_settings;
  52. FSTFirestoreClient *_client;
  53. }
  54. + (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
  55. static dispatch_once_t token = 0;
  56. static NSMutableDictionary<NSString *, FIRFirestore *> *instances;
  57. dispatch_once(&token, ^{
  58. instances = [NSMutableDictionary dictionary];
  59. });
  60. return instances;
  61. }
  62. + (instancetype)firestore {
  63. FIRApp *app = [FIRApp defaultApp];
  64. if (!app) {
  65. FSTThrowInvalidUsage(@"FIRAppNotConfiguredException",
  66. @"Failed to get FirebaseApp instance. Please call FirebaseApp.configure() "
  67. @"before using Firestore");
  68. }
  69. return [self firestoreForApp:app database:kDefaultDatabaseID];
  70. }
  71. + (instancetype)firestoreForApp:(FIRApp *)app {
  72. return [self firestoreForApp:app database:kDefaultDatabaseID];
  73. }
  74. // TODO(b/62410906): make this public
  75. + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
  76. if (!app) {
  77. FSTThrowInvalidArgument(
  78. @"FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd "
  79. "like to use the default FirebaseApp instance.");
  80. }
  81. if (!database) {
  82. FSTThrowInvalidArgument(
  83. @"database identifier may not be nil. Use '%@' if you want the default "
  84. "database",
  85. kDefaultDatabaseID);
  86. }
  87. NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
  88. NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;
  89. @synchronized(instances) {
  90. FIRFirestore *firestore = instances[key];
  91. if (!firestore) {
  92. NSString *projectID = app.options.projectID;
  93. FSTAssert(projectID, @"FirebaseOptions.projectID cannot be nil.");
  94. FSTDispatchQueue *workerDispatchQueue = [FSTDispatchQueue
  95. queueWith:dispatch_queue_create("com.google.firebase.firestore", DISPATCH_QUEUE_SERIAL)];
  96. id<FSTCredentialsProvider> credentialsProvider;
  97. credentialsProvider = [[FSTFirebaseCredentialsProvider alloc] initWithApp:app];
  98. NSString *persistenceKey = app.name;
  99. firestore = [[FIRFirestore alloc] initWithProjectID:projectID
  100. database:database
  101. persistenceKey:persistenceKey
  102. credentialsProvider:credentialsProvider
  103. workerDispatchQueue:workerDispatchQueue
  104. firebaseApp:app];
  105. instances[key] = firestore;
  106. }
  107. return firestore;
  108. }
  109. }
  110. - (instancetype)initWithProjectID:(NSString *)projectID
  111. database:(NSString *)database
  112. persistenceKey:(NSString *)persistenceKey
  113. credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
  114. workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
  115. firebaseApp:(FIRApp *)app {
  116. if (self = [super init]) {
  117. _databaseID = [FSTDatabaseID databaseIDWithProject:projectID database:database];
  118. FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) {
  119. if ([input isKindOfClass:[FIRDocumentReference class]]) {
  120. FIRDocumentReference *documentReference = (FIRDocumentReference *)input;
  121. return [[FSTDocumentKeyReference alloc] initWithKey:documentReference.key
  122. databaseID:documentReference.firestore.databaseID];
  123. } else {
  124. return input;
  125. }
  126. };
  127. _dataConverter =
  128. [[FSTUserDataConverter alloc] initWithDatabaseID:_databaseID preConverter:block];
  129. _persistenceKey = persistenceKey;
  130. _credentialsProvider = credentialsProvider;
  131. _workerDispatchQueue = workerDispatchQueue;
  132. _app = app;
  133. _settings = [[FIRFirestoreSettings alloc] init];
  134. }
  135. return self;
  136. }
  137. - (FIRFirestoreSettings *)settings {
  138. @synchronized(self) {
  139. // Disallow mutation of our internal settings
  140. return [_settings copy];
  141. }
  142. }
  143. - (void)setSettings:(FIRFirestoreSettings *)settings {
  144. @synchronized(self) {
  145. // As a special exception, don't throw if the same settings are passed repeatedly. This should
  146. // make it more friendly to create a Firestore instance.
  147. if (_client && ![_settings isEqual:settings]) {
  148. FSTThrowInvalidUsage(@"FIRIllegalStateException",
  149. @"Firestore instance has already been started and its settings can no "
  150. "longer be changed. You can only set settings before calling any "
  151. "other methods on a Firestore instance.");
  152. }
  153. _settings = [settings copy];
  154. }
  155. }
  156. /**
  157. * Ensures that the FirestoreClient is configured and returns it.
  158. */
  159. - (FSTFirestoreClient *)client {
  160. [self ensureClientConfigured];
  161. return _client;
  162. }
  163. - (void)ensureClientConfigured {
  164. @synchronized(self) {
  165. if (!_client) {
  166. // These values are validated elsewhere; this is just double-checking:
  167. FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
  168. FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
  169. FSTDatabaseInfo *databaseInfo =
  170. [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
  171. persistenceKey:_persistenceKey
  172. host:_settings.host
  173. sslEnabled:_settings.sslEnabled];
  174. FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
  175. _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
  176. usePersistence:_settings.persistenceEnabled
  177. credentialsProvider:_credentialsProvider
  178. userDispatchQueue:userDispatchQueue
  179. workerDispatchQueue:_workerDispatchQueue];
  180. }
  181. }
  182. }
  183. - (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
  184. if (!collectionPath) {
  185. FSTThrowInvalidArgument(@"Collection path cannot be nil.");
  186. }
  187. [self ensureClientConfigured];
  188. FSTResourcePath *path = [FSTResourcePath pathWithString:collectionPath];
  189. return [FIRCollectionReference referenceWithPath:path firestore:self];
  190. }
  191. - (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
  192. if (!documentPath) {
  193. FSTThrowInvalidArgument(@"Document path cannot be nil.");
  194. }
  195. [self ensureClientConfigured];
  196. FSTResourcePath *path = [FSTResourcePath pathWithString:documentPath];
  197. return [FIRDocumentReference referenceWithPath:path firestore:self];
  198. }
  199. - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
  200. dispatchQueue:(dispatch_queue_t)queue
  201. completion:
  202. (void (^)(id _Nullable result, NSError *_Nullable error))completion {
  203. // We wrap the function they provide in order to use internal implementation classes for
  204. // FSTTransaction, and to run the user callback block on the proper queue.
  205. if (!updateBlock) {
  206. FSTThrowInvalidArgument(@"Transaction block cannot be nil.");
  207. } else if (!completion) {
  208. FSTThrowInvalidArgument(@"Transaction completion block cannot be nil.");
  209. }
  210. FSTTransactionBlock wrappedUpdate =
  211. ^(FSTTransaction *internalTransaction,
  212. void (^internalCompletion)(id _Nullable, NSError *_Nullable)) {
  213. FIRTransaction *transaction =
  214. [FIRTransaction transactionWithFSTTransaction:internalTransaction firestore:self];
  215. dispatch_async(queue, ^{
  216. NSError *_Nullable error = nil;
  217. id _Nullable result = updateBlock(transaction, &error);
  218. if (error) {
  219. // Force the result to be nil in the case of an error, in case the user set both.
  220. result = nil;
  221. }
  222. internalCompletion(result, error);
  223. });
  224. };
  225. [self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
  226. }
  227. - (FIRWriteBatch *)batch {
  228. [self ensureClientConfigured];
  229. return [FIRWriteBatch writeBatchWithFirestore:self];
  230. }
  231. - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
  232. completion:
  233. (void (^)(id _Nullable result, NSError *_Nullable error))completion {
  234. static dispatch_queue_t transactionDispatchQueue;
  235. static dispatch_once_t onceToken;
  236. dispatch_once(&onceToken, ^{
  237. transactionDispatchQueue = dispatch_queue_create("com.google.firebase.firestore.transaction",
  238. DISPATCH_QUEUE_CONCURRENT);
  239. });
  240. [self runTransactionWithBlock:updateBlock
  241. dispatchQueue:transactionDispatchQueue
  242. completion:completion];
  243. }
  244. - (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
  245. FSTFirestoreClient *client;
  246. @synchronized(self) {
  247. client = _client;
  248. _client = nil;
  249. }
  250. if (!client) {
  251. // We should be dispatching the callback on the user dispatch queue but if the client is nil
  252. // here that queue was never created.
  253. completion(nil);
  254. } else {
  255. [client shutdownWithCompletion:completion];
  256. }
  257. }
  258. + (BOOL)isLoggingEnabled {
  259. return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO);
  260. }
  261. + (void)enableLogging:(BOOL)logging {
  262. FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
  263. }
  264. - (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
  265. [self ensureClientConfigured];
  266. [self.client enableNetworkWithCompletion:completion];
  267. }
  268. - (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable))completion {
  269. [self ensureClientConfigured];
  270. [self.client disableNetworkWithCompletion:completion];
  271. }
  272. @end
  273. NS_ASSUME_NONNULL_END