FIRFirestore.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. @property(nonatomic, strong) FSTFirestoreClient *client;
  45. @property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
  46. @end
  47. @implementation FIRFirestore {
  48. FIRFirestoreSettings *_settings;
  49. }
  50. + (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
  51. static dispatch_once_t token = 0;
  52. static NSMutableDictionary<NSString *, FIRFirestore *> *instances;
  53. dispatch_once(&token, ^{
  54. instances = [NSMutableDictionary dictionary];
  55. });
  56. return instances;
  57. }
  58. + (instancetype)firestore {
  59. FIRApp *app = [FIRApp defaultApp];
  60. if (!app) {
  61. FSTThrowInvalidUsage(@"FIRAppNotConfiguredException",
  62. @"Failed to get FirebaseApp instance. Please call FirebaseApp.configure() "
  63. @"before using Firestore");
  64. }
  65. return [self firestoreForApp:app database:kDefaultDatabaseID];
  66. }
  67. + (instancetype)firestoreForApp:(FIRApp *)app {
  68. return [self firestoreForApp:app database:kDefaultDatabaseID];
  69. }
  70. // TODO(b/62410906): make this public
  71. + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
  72. if (!app) {
  73. FSTThrowInvalidArgument(
  74. @"FirebaseApp instance may not be nil. Use FirebaseApp.app() if you'd "
  75. "like to use the default FirebaseApp instance.");
  76. }
  77. if (!database) {
  78. FSTThrowInvalidArgument(
  79. @"database identifier may not be nil. Use '%@' if you want the default "
  80. "database",
  81. kDefaultDatabaseID);
  82. }
  83. NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
  84. NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;
  85. @synchronized(instances) {
  86. FIRFirestore *firestore = instances[key];
  87. if (!firestore) {
  88. NSString *projectID = app.options.projectID;
  89. FSTAssert(projectID, @"FirebaseOptions.projectID cannot be nil.");
  90. FSTDispatchQueue *workerDispatchQueue = [FSTDispatchQueue
  91. queueWith:dispatch_queue_create("com.google.firebase.firestore", DISPATCH_QUEUE_SERIAL)];
  92. id<FSTCredentialsProvider> credentialsProvider;
  93. credentialsProvider = [[FSTFirebaseCredentialsProvider alloc] initWithApp:app];
  94. NSString *persistenceKey = app.name;
  95. firestore = [[FIRFirestore alloc] initWithProjectID:projectID
  96. database:database
  97. persistenceKey:persistenceKey
  98. credentialsProvider:credentialsProvider
  99. workerDispatchQueue:workerDispatchQueue
  100. firebaseApp:app];
  101. instances[key] = firestore;
  102. }
  103. return firestore;
  104. }
  105. }
  106. - (instancetype)initWithProjectID:(NSString *)projectID
  107. database:(NSString *)database
  108. persistenceKey:(NSString *)persistenceKey
  109. credentialsProvider:(id<FSTCredentialsProvider>)credentialsProvider
  110. workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue
  111. firebaseApp:(FIRApp *)app {
  112. if (self = [super init]) {
  113. _databaseID = [FSTDatabaseID databaseIDWithProject:projectID database:database];
  114. FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) {
  115. if ([input isKindOfClass:[FIRDocumentReference class]]) {
  116. FIRDocumentReference *documentReference = (FIRDocumentReference *)input;
  117. return [[FSTDocumentKeyReference alloc] initWithKey:documentReference.key
  118. databaseID:documentReference.firestore.databaseID];
  119. } else {
  120. return input;
  121. }
  122. };
  123. _dataConverter =
  124. [[FSTUserDataConverter alloc] initWithDatabaseID:_databaseID preConverter:block];
  125. _persistenceKey = persistenceKey;
  126. _credentialsProvider = credentialsProvider;
  127. _workerDispatchQueue = workerDispatchQueue;
  128. _app = app;
  129. _settings = [[FIRFirestoreSettings alloc] init];
  130. }
  131. return self;
  132. }
  133. - (FIRFirestoreSettings *)settings {
  134. // Disallow mutation of our internal settings
  135. return [_settings copy];
  136. }
  137. - (void)setSettings:(FIRFirestoreSettings *)settings {
  138. // As a special exception, don't throw if the same settings are passed repeatedly. This should
  139. // make it more friendly to create a Firestore instance.
  140. if (_client && ![_settings isEqual:settings]) {
  141. FSTThrowInvalidUsage(@"FIRIllegalStateException",
  142. @"Firestore instance has already been started and its settings can no "
  143. "longer be changed. You can only set settings before calling any "
  144. "other methods on a Firestore instance.");
  145. }
  146. _settings = [settings copy];
  147. }
  148. /**
  149. * Ensures that the FirestoreClient is configured.
  150. * @return self
  151. */
  152. - (instancetype)firestoreWithConfiguredClient {
  153. if (!_client) {
  154. // These values are validated elsewhere; this is just double-checking:
  155. FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
  156. FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
  157. FSTDatabaseInfo *databaseInfo =
  158. [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
  159. persistenceKey:_persistenceKey
  160. host:_settings.host
  161. sslEnabled:_settings.sslEnabled];
  162. FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
  163. _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
  164. usePersistence:_settings.persistenceEnabled
  165. credentialsProvider:_credentialsProvider
  166. userDispatchQueue:userDispatchQueue
  167. workerDispatchQueue:_workerDispatchQueue];
  168. }
  169. return self;
  170. }
  171. - (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
  172. if (!collectionPath) {
  173. FSTThrowInvalidArgument(@"Collection path cannot be nil.");
  174. }
  175. FSTResourcePath *path = [FSTResourcePath pathWithString:collectionPath];
  176. return
  177. [FIRCollectionReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
  178. }
  179. - (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
  180. if (!documentPath) {
  181. FSTThrowInvalidArgument(@"Document path cannot be nil.");
  182. }
  183. FSTResourcePath *path = [FSTResourcePath pathWithString:documentPath];
  184. return [FIRDocumentReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
  185. }
  186. - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
  187. dispatchQueue:(dispatch_queue_t)queue
  188. completion:
  189. (void (^)(id _Nullable result, NSError *_Nullable error))completion {
  190. // We wrap the function they provide in order to use internal implementation classes for
  191. // FSTTransaction, and to run the user callback block on the proper queue.
  192. if (!updateBlock) {
  193. FSTThrowInvalidArgument(@"Transaction block cannot be nil.");
  194. } else if (!completion) {
  195. FSTThrowInvalidArgument(@"Transaction completion block cannot be nil.");
  196. }
  197. FSTTransactionBlock wrappedUpdate =
  198. ^(FSTTransaction *internalTransaction,
  199. void (^internalCompletion)(id _Nullable, NSError *_Nullable)) {
  200. FIRTransaction *transaction =
  201. [FIRTransaction transactionWithFSTTransaction:internalTransaction firestore:self];
  202. dispatch_async(queue, ^{
  203. NSError *_Nullable error = nil;
  204. id _Nullable result = updateBlock(transaction, &error);
  205. if (error) {
  206. // Force the result to be nil in the case of an error, in case the user set both.
  207. result = nil;
  208. }
  209. internalCompletion(result, error);
  210. });
  211. };
  212. [self firestoreWithConfiguredClient];
  213. [self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
  214. }
  215. - (FIRWriteBatch *)batch {
  216. return [FIRWriteBatch writeBatchWithFirestore:[self firestoreWithConfiguredClient]];
  217. }
  218. - (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
  219. completion:
  220. (void (^)(id _Nullable result, NSError *_Nullable error))completion {
  221. static dispatch_queue_t transactionDispatchQueue;
  222. static dispatch_once_t onceToken;
  223. dispatch_once(&onceToken, ^{
  224. transactionDispatchQueue = dispatch_queue_create("com.google.firebase.firestore.transaction",
  225. DISPATCH_QUEUE_CONCURRENT);
  226. });
  227. [self runTransactionWithBlock:updateBlock
  228. dispatchQueue:transactionDispatchQueue
  229. completion:completion];
  230. }
  231. - (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
  232. if (!self.client) {
  233. completion(nil);
  234. return;
  235. }
  236. return [self.client shutdownWithCompletion:completion];
  237. }
  238. + (BOOL)isLoggingEnabled {
  239. return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO);
  240. }
  241. + (void)enableLogging:(BOOL)logging {
  242. FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
  243. }
  244. @end
  245. NS_ASSUME_NONNULL_END