Is there a way to queue function calls? - swift

For communication with backend during the checkout process I have the async functions:
create() : Creates the cart on backend. Called when user segues to the checkout page.
update() : Edits the cart on backend. Called when user edits the cart.
confirm() : Confirms purchase on backend. Called when user places the order.
update() is dependent on response from create(), confirm() is dependent on response from create()/update()
The user can call one function while another is unfinished e.g edits cart shortly after segue to checkout page. This causes problems due to the dependencies.
I have currently semi-solved it by using the bools processing, shouldUpdate and shouldConfirm.
Is there a way to achieve by using a queue where the next function call waits until the previous has finished?
var processing = false // Set true when a function is executing
var shouldUpdate = false // Set true when user edits cart
var shouldConfirm = false // Set true when user taps "Purchase"
var checkoutID = ""
func create() {
processing = true
APIClient.sharedClient.createShoppingCart() {
(checkoutID, error) in
...
processing = false // Finished with network call
if shouldUpdate { // if edit was done while create() is running
update()
shouldUpdate = false
}
if shouldConfirm { // if user tapped "Purchase" while create() is running
confirm()
}
}
}
func update() { // Called from view controller or create()
if processing {return}
processing = true
APIClient.sharedClient.updateShoppingCart(forCheckoutID: checkoutID) {
(error) in
...
processing = false // Finished with network call
if shouldConfirm { // if user tapped "Purchase" while update() is running
confirm()
}
}
}
func confirm() { // Called from view controller or create()/update()
if processing {return}
APIClient.sharedClient.confirmPurchase(forCheckoutID: checkoutID) {
(error) in
...
/// Finish order process
}
}

I personally use PromiseKit - Nice article generally here, wrapping async here - and how to promises here
// your stack
var promises = [];
// add to your stack
promises.push(promise); // some promise func, see above links
promises.push(promise2);
// work the stack
when(fulfilled: promiseArray).then { results in
// Do something
}.catch { error in
// Handle error
}
Keywords for similar solutions: Promises, Deferred, Async Stacks.
or:
You could implement the following:
Have a pool, array of tupel: methodhandler and bool (=executed true)
create a func(1) runs all funcs from the array, in another wrapper function(2) that will set the tupels bool when it is executed.
func(1) will wait till the tupel is changed, and grab the next one.

You can use Dispatch Group
let apiDispatchGroup = DispatchGroup()
func asyncCall1() {
apiDispatchGroup.enter()
print("Entered")
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
/// After 3 Second it will notify main Queue
print("Task 1 Performmed")
/// Let's notify
apiDispatchGroup.leave()
}
apiDispatchGroup.notify(queue: .main) {
/// Perform task 2
asyncCall2()
}
}
func asyncCall2() {
print("Task 2")
}

Related

Swift Concurrency async/await equivalent of a dispatch barrier / semaphore [duplicate]

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

Make tasks in Swift concurrency run serially

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

Unit testing a class that depends on an async function

I have a view model with a state property enum that has 3 cases.
protocol ServiceType {
func doSomething() async
}
#MainActor
final class ViewModel {
enum State {
case notLoaded
case loading
case loaded
}
private let service: ServiceType
var state: State = .notLoaded
init(service: ServiceType) {
self.service = service
}
func load() async {
state = .loading
await service.doSomething()
state = .loaded
}
}
I want to write a unit test that asserts that after load is called but before the async function returns, state == .loading .
If I was using completion handlers, I could create a spy that implements ServiceType, captures that completion handler but doesn't call it. If I was using combine I could use a schedular to control execution.
Is there an equivalent solution when using Swift's new concurrency model?
As you're injecting the depencency via a protocol, you're in a very good position for providing a Fake for that protocol, a fake which you have full control from the unit tests:
class ServiceFake: ServiceType {
var doSomethingReply: (CheckedContinuation<Void, Error>) -> Void = { _ in }
func doSomething() async {
// this creates a continuation, but does nothing with it
// as it waits for the owners to instruct how to proceed
await withCheckedContinuation { doSomethingReply($0) }
}
}
With the above in place, your unit tests are in full control: they know when/if doSomething was called, and can instruct how the function should respond.
final class ViewModelTests: XCTestCase {
func test_viewModelIsLoadingWhileDoSomethingSuspends() {
let serviceFake = ServiceFake()
let viewModel = ViewModel(service: serviceFake)
XCTAssertEquals(viewModel.state, .notLoaded)
let expectation = XCTestExpectation(description: "doSomething() was called")
// just fulfilling the expectation, because we ignore the continuation
// the execution of `load()` will not pass the `doSomething()` call
serviceFake.doSomethingReply = { _ in
expectation.fulfill()
}
Task {
viewModel.load()
}
wait(for: [expectation], timeout: 0.1)
XCTAssertEqual(viewModel.state, .loading)
}
}
The above test makes sure doSomething() is called, as you likely don't want to validate the view model state until you're sure the execution of load() reached the expected place - afterall, load() is called on a different thread, so we need an expectation to make sure the test properly waits until the thread execution reaches the expected point.
The above technique is very similar to a mock/stub, where the implementation is replaced with a unit-test provided one. You could even go further, and just have an async closure instead of a continuation-based one:
class ServiceFake: ServiceType {
var doSomethingReply: () async -> Void = { }
func doSomething() async {
doSomethingReply()
}
}
, and while this would give even greater control in the unit tests, it also pushes the burden of creating the continuations on those unit tests.
You can handle this the similar way you were handling for completion handler, you have the choice to either delay the completion of doSomething using Task.sleep(nanoseconds:) or you can use continuation to block the execution forever by not resuming it same as you are doing with completion handler.
So your mock ServiceType for the delay test scenario looks like:
struct HangingSevice: ServiceType {
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
try? await Task.sleep(nanoseconds: seconds * 1_000_000_000)
}
}
Or for the forever suspended scenario:
class HangingSevice: ServiceType {
private var continuation: CheckedContinuation<Void, Never>?
deinit {
continuation?.resume()
}
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
await withCheckedContinuation { continuation in
self.continuation?.resume()
self.continuation = continuation
}
}
}

Wait combineLatest until #selector is called

TL;DR;
I need to find out a way to setup combineLatest that processes events only after particular self.myMethod() is called without subscribing in that method.
Description
My component A has a subscribe() routin in init(), where all Rx subscriptions are set up.
import RxSwift
final class A {
let bag = DisposeBag()
init() {
//...
subscribe()
}
//...
private func subscribe() {
// Setup all Rx subscriptions here
}
There are two other dependencies B and C, each having their statuses that A needs to combineLatest and yield some UI Event upon that combination.
Observable.combineLatest(b.status,
c.status)
.filter { $0.0 == .connecting && $0.1 == .notReachable }
.map { _ -> Error in
return AError.noInternet
}
.debounce(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] error in
self?.didFail(with: error)
})
.disposed(by: bag)
A is not a UI component and basically handles business logic, thus it should wait until UI "says" it is ready to handle that business logic. E.g., after myMethod() is called on A by UI layer.
Problem
I do want to have the Observable.combineLatest in subscribe() being setup in a way that waits until myMethod() is called and then immediately receives latest events from B's status and C's status.
Currently I do it this way in A:
public func myMethod()
// ...
Observable.combineLatest(...
}
, which breaks the clean code I am striving to.
One thing you could do is make the publisher connectable, and call .connect() when you need to:
let publisher: Publishers.MakeConnectable<AnyPublisher<YourOutput, YourError>>
func subscribe() {
publisher = Observable.combineLatest(b.status, c.status)
.filter { ... }
.map { ... }
.eraseToAnyPublisher()
.makeConnectable()
publisher.subscribe(...)
}
Then, in myMethod() you can do:
func myMethod() {
publisher.connect()
}
Another option would be to add a PublishSubject to your A class.
final class A {
let myMethodCalled = PublishSubject<Void>()
init() {
myMethodCalled
.withLatestFrom(Observable.combineLatest(a.status, b.status))
// etc...
}
func myMethod() {
myMethodCalled.onNext(())
}
}
The above might be a problem if, for example myMethod() is called before a.status and b.status emit any values though.
The best solution is to pass in an Observable that triggers the whole thing instead of calling myMethod(). Embrace the Rx paradigm and get rid of the passive (as opposed to reactive) myMethod().

Making sure a block of code is executed after async function

I'm creating an app using Firestore, I have a function that supposed to add a user to another user's friends list and return true if it was done successfully.
This is the function:
static func addFriendToList(_ id: String) -> Bool {
var friend: Friend!
var isSuccessfullyAddedFriend: Bool = false
let group = DispatchGroup()
DispatchQueue.global(qos: .userInitiated).async {
group.enter()
// Getting user's deatils and creating a Friend object.
FirestoreService.shared.getUserDetailsById(user: id) { (newFriend) in
if newFriend != nil {
friend = newFriend!
}
group.leave()
}
group.wait()
group.enter()
// Adding the new Friend Object to the friends list of the current user
FirestoreService.shared.addUserToFriendsList(friend: friend) { (friendAdded) in
if friendAdded {
isSuccessfullyAddedFriend = true
FirestoreService.shared.fetchFriendList()
}
}
group.leave()
}
group.wait()
return isSuccessfullyAddedFriend
}
My problem is that the addUserToFriendsList is an async function, and the return isSuccessfullyAddedFriend is being executed before it turns to true.
As you can see, I tried to overcome this problem with using DispatchGroup, but with no success, the problem still occurs. Is there another, maybe better way to achieve this?
I need the return line to happen after addUserToFriendsList
You need
static func addFriendToList(_ id: String,completion:#escaping(Bool)->()) {
FirestoreService.shared.getUserDetailsById(user: id) { (newFriend) in
FirestoreService.shared.addUserToFriendsList(friend: newFriend) { (friendAdded) in
if friendAdded {
FirestoreService.shared.fetchFriendList()
completion(true)
}
else {
completion(false)
}
}
}
}
Call
Api.addFriendToList(<#id#>) { flag in
print(flag)
}
2 notes
1- Firebase calls run in a background thread so no need for global queue
2- DispatchGroup is used for multiple concurrent tasks not serial