Swift: withCheckedContinuation and Dispatch QoSClass - swift

I am adapting some old code which was using common completions in order to use the new async/await syntax with Parse SDK. Here is an example, this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
query.getObjectInBackground(withId: id) { object, _ in
completion(object)
}
}
is becoming this:
static func get(
className: String,
objectId: String
) async -> PFObject? {
let query = PFQuery(className: className)
return await withCheckedContinuation { continuation in
query.getObjectInBackground(withId: objectId) { result, _ in
continuation.resume(returning: result)
}
}
}
However, I was also using DispatchQueue/QoS previously and so the old function actually looked like this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
DispatchQueue.global(qos: .userInteractive).async {
query.getObjectInBackground(withId: id) { object, _ in
DispatchQueue.main.async {
completion(object)
}
}
}
}
How can I use this with the async/await syntax? Is it even needed?
Thank you for your help

You probably want to execute your async function get(className:objectId:) within a Swift Task, using this Task initialiser:
func foo(
className: String,
objectId: String
) async -> PFObject? {
await Task(priority: .userInitiated) {
await get(className: className, objectId: objectId)
}
.value
}
Note that, when using this Task initialiser you are getting "structured concurrency", which means, that the embedded task in function foo inherits the actor context of the calling function.
That is, you can use the result safely from whatever thread you called it.
That means also, if the task where function foo() is running, gets cancelled, the embedded task will be cancelled as well. Of course, cancellation is cooperatively, which means, you need to stop a running task explicitly. For example, as preferred in your use case, with a withTaskCancellationHandler, which calls cancel() on your PFQuery object. Or when you have a long running iterating task, you may poll the Task's cancellation status in reasonably steps while your task progresses.
Please also read about "detached" which behave differently regarding cancellation and inheriting the task priority.
As of your question whether it is needed:
I assume, you ask if using specifying a priority is needed:
Short answer: in many use cases it may be more safe to just inherit the priority from whatever thread the function originates.
In your case, I would not explicitly change it: when it is called from a background thread with low priority, your task should inheriting this priority as well. There will be no reason to make it high priority with userInitiated. Otherwise, if your function is already called from a user action, it will be inheriting this higher priority as well. I believe, this is what most use cases would require.
Also, when your worker is running in a background thread, as is the case in your code, it really doesn't matter much "how fast you schedule" this task.
So, basically you end up with your async get function as defined, use it as is, and all is good. ;)

Related

Swift How to wait until a specific condition is met

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.

Swift how to test async call wrapped in a function

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?

What's difference between `add(_)` and `add(_) async`?

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.

XCTest: Wait for async call to finish in synchronous function [duplicate]

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?

`NSDocument`'s `data(ofType:)` getting data from (async) `actor`

I've a document based macOS, that's using a NSDocument based subclass.
For writing the document's file I need to implement data(ofType:) -> Data which should return the document's data to be stored on disk. This is (of course) a synchronous function.
My data model is an actor with a function that returns a Data representation.
The problem is now that I need to await this function, but data(ofType:) wants the data synchronously.
How can I force-wait (block the main thread) until the actor has done its work and get the data?
EDIT:
In light of Sweepers remark that this might be an XY-problem I tried making the model a #MainActor, so the document can access the properties directly. This however doesn't allow me to create the model in the first place:
#MainActor class Model {}
class Document: NSDocument {
let model = Model() <- 'Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context'
}
I then tried to make the whole Document a #MainActor, but that makes my whole app to collapse in compiler errors. Even the simplest of calls need to be performed async. This doesn't allow any kind of upgrade path to the new concurrency system.
In the past my model was protected by a serial background queue and I could basically do queue.sync {} to get the needed data out safely (temporarily blocking the main queue).
I've looked into the saveToURL:ofType:forSaveOperation:completionHandler: and I think I can use this very much to my need. It allows async messaging that saving is finished, so I now override this method and in an async Task fetch the data from the model and store it in temporarily. I then call super, which finally calls data(forType:) where I return the data.
Based on the idea by #Willeke in the comments, I came up with the following solution:
private var snapshot: Model.Snapshot?
override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, completionHandler: #escaping (Error?) -> Void) {
//Get the data and continue later
Task {
snapshot = await model.getSnapshot()
super.save(to: url, ofType: typeName, for: saveOperation, completionHandler: completionHandler)
}
}
override func data(ofType typeName: String) throws -> Data {
defer { snapshot = nil }
guard let snapshot = snapshot else {
throw SomeError()
}
let encoder = JSONEncoder()
let data = try encoder.encode(snapshot)
return data
}
As the save() function is prepared to handle the save result asynchronous we first take the snapshot of the data and then let the save function continue.