Transaction+Combine.swift 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #if canImport(Combine) && swift(>=5.0)
  15. import Combine
  16. import FirebaseFirestore
  17. @available(swift 5.0)
  18. @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)
  19. public extension Firestore {
  20. /// Executes the given updateBlock and then attempts to commit the changes applied within an
  21. /// atomic transaction.
  22. ///
  23. /// The maximum number of writes allowed in a single transaction is 500, but note that each
  24. /// usage of `FieldValue.serverTimestamp()`, `FieldValue.arrayUnion()`,
  25. /// `FieldValue.arrayRemove()`, or `FieldValue.increment()` inside a transaction counts as an
  26. /// additional write.
  27. ///
  28. /// In the updateBlock, a set of reads and writes can be performed atomically using the
  29. /// `Transaction` object passed to the block. After the updateBlock is run, Firestore will
  30. /// attempt to apply the changes to the server. If any of the data read has been modified
  31. /// outside of this transaction since being read, then the transaction will be retried by
  32. /// executing the updateBlock again. If the transaction still fails after 5 retries, then the
  33. /// transaction will fail.
  34. ///
  35. /// Since the updateBlock may be executed multiple times, it should avoiding doing anything that
  36. /// would cause side effects.
  37. ///
  38. /// Any value maybe be returned from the updateBlock. If the transaction is successfully
  39. /// committed, then the completion block will be passed that value. The updateBlock also has an
  40. /// `Error` out parameter. If this is set, then the transaction will not attempt to commit, and
  41. /// the given error will be passed to the completion block.
  42. ///
  43. /// The `Transaction` object passed to the updateBlock contains methods for accessing documents
  44. /// and collections. Unlike other firestore access, data accessed with the transaction will not
  45. /// reflect local changes that have not been committed. For this reason, it is required that
  46. /// all reads are performed before any writes. Transactions must be performed while online.
  47. /// Otherwise, reads will fail, the final commit will fail, and the completion block will
  48. /// return an error.
  49. ///
  50. /// - Parameter updateBlock: The block to execute within the transaction context.
  51. /// - Returns: A publisher emitting a value instance passed from the updateBlock. This block
  52. /// will run even if the client is offline, unless the process is killed.
  53. func runTransaction<T>(_ updateBlock: @escaping (Transaction) throws -> T)
  54. -> Future<T, Error> {
  55. Future { promise in
  56. self.runTransaction({ transaction, errorPointer in
  57. do {
  58. return try updateBlock(transaction)
  59. } catch let fetchError as NSError {
  60. errorPointer?.pointee = fetchError
  61. return nil
  62. }
  63. }) { value, error in
  64. if let error = error {
  65. promise(.failure(error))
  66. } else if let value = value as? T {
  67. promise(.success(value))
  68. }
  69. }
  70. }
  71. }
  72. }
  73. #endif