async and await for Swift.
Maybe I am putting the cart before the horses, but... For all the functions in the next section you can await the returning value in the same way:
func someFuncRunningInBackground() throws {
let value = try await { self.someLongRunningAsynchronousFunc() }
// do something with the returned value...
}
You can also use the result only when needed:
func someFuncRunningInBackground() throws {
// the task starts immediately
let task = self.someLongRunningAsynchronousFunc()
// do other things, the task is running...
// continue doing other things...
// the task can be still running or maybe it is already finished, who knows?
//
let value = try await(task)
// do something with the returned value...
}
You can simply return the desired value inside the async
closure.
func someLongRunningAsynchronousFunc -> Task<SomeValuableValue> {
return async {
let value = self.createSomeValuableValue()
// some long running code here...
return value
}
}
If you are calling a method with completion handler, however, you may need a different closure signature with the task itself as parameter and call its finish(with:)
method when the work is finished.
func someLongRunningAsynchronousFunc -> Task<SomeValuableValue> {
return async { task in
self.getSomeValuableValue(completionHandler: { value in
task.finish(with: value)
})
}
}
If the completion handler has an error as additional parameter, you can pass it to the finish
method too (and it will be handled in the try
statement when awaiting for the task result).
func someLongRunningAsynchronousFunc -> Task<SomeValuableValue> {
return async { task in
self.getSomeValuableValue(completionHandler: { value, error in
task.finish(with: value, or: error)
})
}
}
You can also create tasks using convenience methods added to DispatchQueue
and OperationQueue
:
func calculate() -> Double {
// using DispatchQueue
let someCalculatedValue = await(DispatchQueue.global(qos: .background).async {
var value: Double = 0
// do some long running calculation here
return value
} as NonFailableTask<Double>)
// using OperationQueue
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .background
let operationTask = operationQueue.addOperation {
var value: Double = 0
// do some long running calculation here
return value
} as NonFailableTask<Double>
// using the results
return someCalculatedValue * await(operationTask)
}
You can cancel a task after it was enqueued using its cancel()
method. When it will be actually cancelled depends on the implementation of its content, however.
Cancelling a task in AlecrimAsyncKit is pretty the same as finishing it with an NSError
with NSCocoaErrorDomain
and NSUserCancelledError
as parameters. But if you use cancel()
cancellation actions can be fired using provided blocks.
You can add cancellation blocks to be executed when a task is cancelled this way:
func someLongRunningAsynchronousFunc -> Task<SomeValuableValue> {
return async { task in
let token = self.getSomeValuableValue(completionHandler: { value, error in
task.finish(with: value, or: error)
})
// add a closure to be executed when and if the task is cancelled
task.cancellation += {
token.invalidate()
}
}
}
Of course this can be done only if you are using the "extended" async closure signature as you will may need the task
parameter.
The framework will try to "inherit" cancellation for child tasks when possible. This means that if a parent task is cancelled, the tasks that started within its implementation may be also cancelled without major interventions.
Since the await
func blocks the current thread, you can only await an async func on a background DispatchQueue
, OperationQueue
or Thread
. However you can do the following in the main thread:
func someFuncRunningInTheMainThread() {
//
self.activityIndicator.startAnimating()
// start the background task
self.someLongRunningAsynchronousFunc()
.then { value in
// when the background work is done,
// do something with the returned value in the main thread
}
.catch { error in
// do a nice error handling
}
.finally {
self.activityIndicator.stopAnimating()
}
}
All methods (then
, catch
, cancelled
and finally
) are optional. When specified, the closure related to the finally
method will always be called regardless whether the task was cancelled or not, whether there was an error or not.
If you read the framework's code you will find the NonFailableTask<Value>
class. This kind of task cannot fail (sort of). In fact it may fail, but it should not.
The main difference from the failable task class is that you do not have to try
when awaiting for non failable task results. A non failable task cannot be cancelled either.
Please only use this type of task when you are sure that it cannot fail. If it do and the task fail your program will crash.
The previous version had observers and conditions based on Session 226 of WWDC 2015 (“Advanced NSOperations”). This turned the framework unnecessarily complex. If you need this functionality right now you can use version 3.x of AlecrimAsyncKit.
In version 4.0 the framework has the notion of "dependencies" and a new kind of "conditions" was implemented. Observers may reappear in a future release. No guarantees, though.
If you have any problems or need more information, please open an issue using the provided GitHub link.
You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submitted patches.
- master - The production branch. Clone or fork this repository for the latest copy.
- develop - The active development branch. Pull requests should be directed to this branch.
AlecrimAsyncKit is released under an MIT license. See LICENSE for more information.