Skip to content

Commit dfe4218

Browse files
authored
Merge 0159f99 into c1a33a0
2 parents c1a33a0 + 0159f99 commit dfe4218

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

Firestore/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Unreleased
22
- [changed] Add more details to the assertion failure in Query::Comparator() to
33
help with future debugging (#9258).
4+
- [fixed] **Breaking change:** Fixed an issue where returning `nil` from the
5+
update closure when running a transaction caused a crash in Swift by removing
6+
the auto-generated `async throw`ing method from the `FirebaseFirestore`
7+
module. In order to use the `async throw`ing transaction method, add the
8+
`FirebaseFirestoreSwift` module dependency to your build target (#9426).
49

510
# v8.14.0
611
- [fixed] Fixed compiler warnings in `local_serializer.cc` about "implicit

Firestore/Source/Public/FirebaseFirestore/FIRFirestore.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ NS_SWIFT_NAME(Firestore)
119119
* `FieldValue.increment()` inside a transaction counts as an additional write.
120120
*
121121
* In the updateBlock, a set of reads and writes can be performed atomically using the
122-
* `FIRTransaction` object passed to the block. After the updateBlock is run, Firestore will attempt
122+
* `Transaction` object passed to the block. After the updateBlock is run, Firestore will attempt
123123
* to apply the changes to the server. If any of the data read has been modified outside of this
124124
* transaction since being read, then the transaction will be retried by executing the updateBlock
125125
* again. If the transaction still fails after 5 retries, then the transaction will fail.
@@ -132,7 +132,7 @@ NS_SWIFT_NAME(Firestore)
132132
* parameter. If this is set, then the transaction will not attempt to commit, and the given error
133133
* will be passed to the completion block.
134134
*
135-
* The `FIRTransaction` object passed to the updateBlock contains methods for accessing documents
135+
* The `Transaction` object passed to the updateBlock contains methods for accessing documents
136136
* and collections. Unlike other firestore access, data accessed with the transaction will not
137137
* reflect local changes that have not been committed. For this reason, it is required that all
138138
* reads are performed before any writes. Transactions must be performed while online. Otherwise,
@@ -143,7 +143,8 @@ NS_SWIFT_NAME(Firestore)
143143
* block will run even if the client is offline, unless the process is killed.
144144
*/
145145
- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
146-
completion:(void (^)(id _Nullable result, NSError *_Nullable error))completion;
146+
completion:(void (^)(id _Nullable result, NSError *_Nullable error))completion
147+
__attribute__((swift_async(none))); // Disable async import due to #9426.
147148

148149
/**
149150
* Creates a write batch, used for performing multiple writes as a single

Firestore/Swift/Source/AsyncAwait/Firestore+AsyncAwait.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,50 @@ import Foundation
5353
}
5454
}
5555
}
56+
57+
/// Executes the given updateBlock and then attempts to commit the changes applied within an atomic
58+
/// transaction.
59+
///
60+
/// The maximum number of writes allowed in a single transaction is 500, but note that each usage of
61+
/// `FieldValue.serverTimestamp()`, `FieldValue.arrayUnion()`, `FieldValue.arrayRemove()`, or
62+
/// `FieldValue.increment()` inside a transaction counts as an additional write.
63+
///
64+
/// In the `updateBlock`, a set of reads and writes can be performed atomically using the
65+
/// `Transaction` object passed to the block. After the `updateBlock` is run, Firestore will attempt
66+
/// to apply the changes to the server. If any of the data read has been modified outside of this
67+
/// transaction since being read, then the transaction will be retried by executing the `updateBlock`
68+
/// again. If the transaction still fails after 5 retries, then the transaction will fail.
69+
///
70+
/// Since the `updateBlock` may be executed multiple times, it should avoiding doing anything that
71+
/// would cause side effects.
72+
///
73+
/// Any value maybe be returned from the `updateBlock`. If the transaction is successfully committed,
74+
/// then the completion block will be passed that value. The `updateBlock` also has an `NSError` out
75+
/// parameter. If this is set, then the transaction will not attempt to commit, and the given error
76+
/// will be returned.
77+
///
78+
/// The `Transaction` object passed to the `updateBlock` contains methods for accessing documents
79+
/// and collections. Unlike other firestore access, data accessed with the transaction will not
80+
/// reflect local changes that have not been committed. For this reason, it is required that all
81+
/// reads are performed before any writes. Transactions must be performed while online. Otherwise,
82+
/// reads will fail, the final commit will fail, and this function will return an error.
83+
///
84+
/// - Parameter updateBlock The block to execute within the transaction context.
85+
/// - Throws Throws an error if the transaction could not be committed, or if an error was explicitly specified in the `updateBlock` parameter.
86+
/// - Returns Returns the value returned in the `updateBlock` parameter if no errors occurred.
87+
func runTransaction(_ updateBlock: @escaping (Transaction, NSErrorPointer)
88+
-> Any?) async throws -> Any? {
89+
// This needs to be wrapped in order to express a nullable return value upon success.
90+
// See https://github.com/firebase/firebase-ios-sdk/issues/9426 for more details.
91+
return try await withCheckedThrowingContinuation { continuation in
92+
self.runTransaction(updateBlock) { anyValue, error in
93+
if let err = error {
94+
continuation.resume(throwing: err)
95+
} else {
96+
continuation.resume(returning: anyValue)
97+
}
98+
}
99+
}
100+
}
56101
}
57102
#endif

Firestore/Swift/Tests/Integration/AsyncAwaitIntegrationTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,13 @@ let emptyBundle = """
6565
.loadBundle(InputStream(data: bundle.data(using: String.Encoding.utf8)!))
6666
XCTAssertEqual(LoadBundleTaskState.success, bundleProgress.state)
6767
}
68+
69+
func testRunTransactionDoesNotCrashOnNilSuccess() async throws {
70+
let value = try await db.runTransaction { transact, error in
71+
nil // should not crash
72+
}
73+
74+
XCTAssertNil(value, "value should be nil on success")
75+
}
6876
}
6977
#endif

0 commit comments

Comments
 (0)