belozierov/SwiftCoroutine
Swift coroutines for iOS, macOS and Linux.
Working with SwiftCoroutine
### Coroutines
A [coroutine](https://en.wikipedia.org/wiki/Coroutine) is a computation that can be suspended and resumed at a later time without blocking a thread. Coroutines build upon regular functions and can be executed on any scheduler with a possibility to switch among them during execution.
#### Key benefits
- **Suspend instead of block**. The main advantage of coroutines is the ability to suspend their execution at some point without blocking a thread and resuming later on.
- **Fast context switching**. Switching between coroutines is much faster than switching between threads as it does not require the involvement of operating system.
- **Asynchronous code in synchronous manner**. The use of coroutines allows an asynchronous, non-blocking function to be structured in a manner similar to an ordinary synchronous function. And even though coroutines can run in multiple threads, your code will still look consistent and therefore easy to understand.
#### Usage
The coroutines API design is as minimalistic as possible. It consists of the `CoroutineScheduler` protocol that describes how to schedule coroutines (`DispatchQueue` already conforms it), and the `Coroutine` structure with utility methods. This API is enough to do amazing things.
The following example shows the usage of `await()` inside a coroutine to wrap asynchronous calls.
```swift
//execute coroutine on the main thread
DispatchQueue.main.startCoroutine {
//await URLSessionDataTask response without blocking the thread
let (data, response, error) = try Coroutine.await { callback in
URLSession.shared.dataTask(with: url, completionHandler: callback).resume()
}
. . . use response on the main thread . . .
}
```
Here's how we can conform `NSManagedObjectContext` to `CoroutineScheduler` for launching coroutines on it.
```swift
extension NSManagedObjectContext: CoroutineScheduler {
func scheduleTask(_ task: @escaping () -> Void) {
perform(task)
}
}
//execute coroutine on the main thread
DispatchQueue.main.startCoroutine {
let context: NSManagedObjectContext //context with privateQueueConcurrencyType
let request: NSFetchRequest<NSDictionary> //some complex request
//execute request on the context without blocking the main thread
let result: [NSDictionary] = try context.await { try context.fetch(request) }
}
```
### Futures and Promises
A future is a read-only holder for a result that will be provided later and the promise is the provider of this result. They represent the eventual completion or failure of an asynchronous operation.
The [futures and promises](https://en.wikipedia.org/wiki/Futures_and_promises) approach itself has become an industry standart. It is a convenient mechanism to synchronize asynchronous code. But together with coroutines, it takes the usage of asynchronous code to the next level and has become a part of the async/await pattern. If coroutines are a skeleton, then futures and promises are its muscles.
#### Main features
- **Performance**. It is much faster than most of other futures and promises implementations.
- **Awaitable**. You can await the result inside the coroutine.
- **Cancellable**. You can cancel the whole chain as well as handle it and complete the related actions.
#### Usage
Futures and promises are represented by the corresponding `CoFuture` class and its `CoPromise` subclass.
```swift
//wraps some async func with CoFuture
func makeIntFuture() -> CoFuture<Int> {
let promise = CoPromise<Int>()
someAsyncFunc { int in
promise.success(int)
}
return promise
}
```
It allows to start multiple tasks in parallel and synchronize them later with `await()`.
```swift
//create CoFuture<Int> that takes 2 sec. from the example above
let future1: CoFuture<Int> = makeIntFuture()
//execute coroutine on the global queue and returns CoFuture<Int> with future result
let future2: CoFuture<Int> = DispatchQueue.global().coroutineFuture {
try Coroutine.delay(.seconds(3)) //some work that takes 3 sec.
return 6
}
//execute coroutine on the main thread
DispatchQueue.main.startCoroutine {
let sum: Int = try future1.await() + future2.await() //will await for 3 sec.
self.label.text = "Sum is \(sum)"
}
```
It's very easy to transform or compose `CoFuture`s into a new one.
```swift
let array: [CoFuture<Int>]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array.reduce(0) { try $0 + $1.await() } }
```
### Channels
Futures and promises provide a convenient way to transfer a single value between coroutines. [Channels](https://en.wikipedia.org/wiki/Channel_(programming)) provide a way to transfer a stream of values. Conceptually, a channel is similar to a queue that allows to suspend a coroutine on receive if it is empty, or on send if it is full.
This non-blocking primitive is widely used in such languages as Go and Kotlin, and it is another instrument that improves working with coroutines.
#### Usage
To create channels, use the `CoChannel` class.
```swift
//create a channel with a buffer which can store only one element
let channel = CoChannel<Int>(capacity: 1)
DispatchQueue.global().startCoroutine {
for i in 0..<100 {
//imitate some work
try Coroutine.delay(.seconds(1))
//sends a value to the channel and suspends coroutine if its buffer is full
try channel.awaitSend(i)
}
//close channel when all values are sent
channel.close()
}
DispatchQueue.global().startCoroutine {
//receives values until closed and suspends a coroutine if it's empty
for i in channel.makeIterator() {
print("Receive", i)
}
print("Done")
}
```
### Scope
All launched coroutines, `CoFuture`s and `CoChannel`s, usually do not need to be referenced. They are deinited after their execution. But often there is a need to complete them earlier, when they are no longer needed. For this, `CoFuture` and `CoChannel` have methods for canceling.
`CoScope` makes it easier to manage the life cycle of these objects. It allows you to keep weak references to them and cancel if necessary or on deinit.
#### Usage
You can add coroutines, `CoFuture`s, `CoChannel`s and other `CoCancellable` to `CoScope` to cancel them when they are no longer needed or on deinit.
```swift
class ViewController: UIViewController {
let scope = CoScope() //will cancel all objects on `cancel()` or deinit
func performSomeWork() {
//create new `CoChannel` and add to `CoScope`
let channel = makeSomeChannel().added(to: scope)
//execute coroutine and add to `CoScope`
DispatchQueue.main.startCoroutine(in: scope) { [weak self] in
for item in channel.makeIterator() {
try self?.performSomeWork(with: item)
}
}
}
func performSomeWork(with item: Item) throws {
//create new `CoFuture` and add to `CoScope`
let future = makeSomeFuture(item).added(to: scope)
let result = try future.await()
. . . do some work using result . . .
}
}
```Package Metadata
Repository: belozierov/SwiftCoroutine
Homepage: https://belozierov.github.io/SwiftCoroutine
Stars: 832
Forks: 50
Open issues: 7
Default branch: master
Primary language: swift
License: MIT
Topics: async, async-await, asynchronous, atomic, await, combine, coroutine, coroutines, dispatch, futures, ios, linux, lock-free, macos, multithreading, promises, scheduler, structured-concurrency, swift, thread
README: README.md
Archived: yes