With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).
Related
I've looked through just about every question on this topic I could find but I've had little success. I need to run a function on an array of actors conforming to a specific actor protocol. Because these are actors, I need an async call. But I also need to run these functions in a specific order, and I'm not going to describe how I get the order, just suffice it to say that I have it. I am also using the following asyncForEach function, though I'm open to not doing this.
extension Sequence {
func asyncForEach (
_ operation: #escaping (Element) async -> Void
) async {
// A task group automatically waits for all of its
// sub-tasks to complete, while also performing those
// tasks in parallel:
await withTaskGroup(of: Void.self) { group in
for element in self {
group.addTask {
await operation(element)
}
}
}
}
}
Now I have some protocol
protocol ProtocolConformingActors: Actor {
func bar() async throws
}
This leads me to running my function
func foo() async throws {
let actorsAndOrders: [Int: ProtocolConformingActors] = [1:actor1, 2:actor2, 3:actor3]
// Get order
var orders: [Int] = []
for entry in actorsAndOrders {
orders.append(entry.key)
}
orders.sort()
// Run func
await orders.asyncForEach { order in
let actor = actorsAndOrders[order]
try await actor?.bar()
}
}
And this is where the problem occurs. Like I mentioned above, these calls need to be async because bar() is modifying isolated properties on each actor. Because in order to make this happen, I need to use the asyncForEach, but as I understand it, the asyncForEach loop sets up and runs each function bar() in parallel. But they need to be run in order.
Is there a way I can make each thread wait until a condition is met?
I was thinking I might be able to use the condition orders[0] == order and when bar() is done running, remove the first entry from the orders array, which could make it tell the next thread it can wake up again.
But while the apple documentation seems to indicate that there is a wait(until:) function on NSCondition, I can't seem to make it work.
Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data
Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.
The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.
I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}
func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?
I don't understand what's the difference between add(_) and add(_) async method. Like the below code, the MyActor has two add methods and one of them uses async keyword. They can exist at the same time. If I comment out the second add method it will output AAAA. If both add methods exist at the same time, output "BBBBB"。
actor MyActor {
var num: Int = 0
func add(_ value: Int) {
print("AAAAA")
num += value
}
func add(_ value: Int) async {
print("BBBBB")
num += value
}
}
let actor = MyActor()
Task {
await actor.add(200)
print(await actor.num)
}
Supplementary:
With the second add method commented out, I defined let actor = MyActor() outside Task and I noticed the add method signed as add(_:). If move let actor = MyActor() inside Task the add method signed as add(_:) async
The difference emerges inside the actor, for example
actor MyActor {
func addSync(_ value: Int) {
}
func addAsync(_ value: Int) async {
}
func f() {
addSync(0)
}
func g() async {
addSync(0)
await addAsync(0)
}
}
In the actor method f and g you can call addSync synchronously. While outside the actor, you need always call an actor method with the await keyword as if the method is asynchronous:
func outside() async {
let actor = MyActor()
await actor.addSync(0)
}
Async in Swift allows for structured concurrency, which will improve the readability of complex asynchronous code. Completion closures are no longer needed, and calling into multiple asynchronous methods after each other is a lot more readable.
Async stands for asynchronous and can be seen as a method attribute making it clear that a method performs asynchronous work. An example of such a method looks as follows:
func fetchImages() async throws -> [UIImage] {
// .. perform data request
}
The fetchImages method is defined as async throwing, which means that it’s performing a failable asynchronous job. The method would return a collection of images if everything went well or throws an error if something went wrong.
How async replaces closure completion callbacks
Async methods replace the often seen closure completion callbacks. Completion callbacks were common in Swift to return from an asynchronous task, often combined with a Result type parameter. The above method would have been written as followed:
func fetchImages(completion: (Result<[UIImage], Error>) -> Void) {
// .. perform data request
}
Defining a method using a completion closure is still possible in Swift today, but it has a few downsides that are solved by using async instead:
You have to make sure yourself to call the completion closure in each possible method exit. Not doing so will possibly result in an app waiting for a result endlessly.
Closures are harder to read. It’s not as easy to reason about the order of execution as compared to how easy it is with structured concurrency.
Retain cycles need to be avoided using weak references.
Implementors need to switch over the result to get the outcome. It’s not possible to use try catch statements from the implementation level.
These downsides are based on the closure version using the relatively new Result enum. It’s likely that a lot of projects still make use of completion callbacks without this enumeration:
func fetchImages(completion: ([UIImage]?, Error?) -> Void) {
// .. perform data request
}
Defining a method like this makes it even harder to reason about the outcome on the caller’s side. Both value and error are optional, which requires us to perform an unwrap in any case. Unwrapping these optionals results in more code clutter which does not help to improve readability.
Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data
Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.
The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.
I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}
func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?
I have an HTTPService which returns an Observable<NSData>. My goal is to compose that service into another service, ServiceA which transforms that data for my use case. Using Observable.create in RxSwift 2.0.0-rc.0 in ServiceA it's straight forward enough. My question is how to properly handle the disposable returned from the subscription of the HTTPService.
If I don't do anything I get the compile time warning that the result of call is unused: http://git.io/rxs.ud. I understand from reading that if I do nothing it's likely ok: (where xs mentioned below is let xs: Observable<E> ....
In case xs terminates in a predictable way with Completed or Error message, not handling subscription Disposable won't leak any resources, but it's still preferred way because in that way element computation is terminated at predictable moment.
So here is how I am currently addressing it, and also where I am wondering if I am doing this properly or if I have misunderstood something.
public struct ServiceA{
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
let disposable = request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
// Is this the right way to deal with the
// disposable from the subscription in this situation?
return AnonymousDisposable{
disposable.dispose()
}
}
}
}
As documentation says
subscribe function returns a subscription Disposable that can be used to cancel computation and free resources.
Preferred way of terminating these fluent calls is by using
.addDisposableTo(disposeBag) or in some equivalent way.
When disposeBag gets deallocated, subscription will be automatically
disposed.
Actually your example looks fine in terms of rules, but it loos pretty bad ;) (Also it would be ok, if you would just return this disposable) :
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
return request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
}
But as you you returning Observeble I wonder, why you dont just use map operator ?
In your example it would be something like this:
public static func changes() -> Observable<ChangeSet> {
return HTTPService.get("https://httpbin.org/get")
.map(ChangeSet.init)
}