Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Swift coroutines for iOS, macOS and Linux.

License

NotificationsYou must be signed in to change notification settings

rebbdif/SwiftCoroutine

 
 

Repository files navigation

Swift Coroutine

macOSUbuntucodecovcodebeat badge

Many languages, such as Kotlin, Go, JavaScript, Python, Rust, C#, C++ and others, already havecoroutines support that makes theasync/await pattern implementation possible. This feature is not yet supported in Swift, but this can be improved by a framework without the need to change the language.

Main features

  • It is the first implementation ofcoroutines for Swift with iOS, macOS and Linux support.
  • It includesfutures andchannels that supplement coroutines for more flexibility.
  • It is completelylock-free and uses onlyatomic primitives for synchronizations.

Motivation

Asynchronous programming is usually associated with callbacks. It is quite convenient until there are too many of them and they start nesting. Then it's called apyramid of doom or evencallback hell.

Another problem of asynchronous programming iserror handling, because Swift's natural error handling mechanism cannot be used.

What about Rx and other such frameworks?

There are many other frameworks that make it easy to use asynchronous code, such as Combine, RxSwift, PromiseKit and so on. They use other approaches that have some drawbacks:

  • Similar to callbacks, you also need to create chained calls, that’s why you can’t normally use loops, exception handling, etc.
  • Usually you need to learn a complex new API with hundreds of methods.
  • Instead of working with the actual data, you need to operate with some wrappers all the time.
  • Chaining of errors can be really complicated to handle.

Async/await

Theasync/await pattern is an alternative that allows an asynchronous, non-blocking function to be structured in a way similar to an ordinary synchronous function.

It is already well-established in other programming languages and is an evolution in asynchronous programming. The implementation of this pattern is possible thanks to coroutines.

Let’s have a look at the example with coroutine inside of whichawait() suspends it and resumes when the result is available without blocking the thread.

//executes coroutine on the main threadDispatchQueue.main.startCoroutine{        //extension that returns CoFuture<(data: Data, response: URLResponse)>letdataFuture=URLSession.shared.dataTaskFuture(for: imageURL)        //await CoFuture result that suspends coroutine and doesn't block the threadletdata:Data=try dataFuture.await().data    //create UIImage from the dataguardlet image=UIImage(data: data)else{return}        //execute heavy task on global queue and await the result without blocking the threadletthumbnail:UIImage=tryDispatchQueue.global().await{ image.makeThumbnail()}    //set image in UIImageView on the main threadself.imageView.image= thumbnail}

Documentation

API documentation

Requirements

  • Support only 64-bit architectures
  • iOS 10+ / macOS 10.12+ / Ubuntu
  • Xcode 10.4+
  • Swift 5.2+

Installation

Working with SwiftCoroutine

Coroutines

Acoroutine 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 theCoroutineScheduler protocol that describes how to schedule coroutines (DispatchQueue already conforms it), and theCoroutine structure with utility methods. This API is enough to do amazing things.

The following example shows the usage ofawait() inside a coroutine to wrap asynchronous calls.

//execute coroutine on the main threadDispatchQueue.main.startCoroutine{        //await URLSessionDataTask response without blocking the threadlet(data, response, error)=tryCoroutine.await{ callbackinURLSession.shared.dataTask(with: url, completionHandler: callback).resume()}... use response on the main thread...}

Here's how we can conformNSManagedObjectContext toCoroutineScheduler for launching coroutines on it.

extensionNSManagedObjectContext:CoroutineScheduler{func scheduleTask(_ task:@escaping()->Void){perform(task)}}//execute coroutine on the main threadDispatchQueue.main.startCoroutine{letcontext:NSManagedObjectContext //context with privateQueueConcurrencyTypeletrequest:NSFetchRequest<NSDictionary> //some complex request    //execute request on the context without blocking the main threadletresult:[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.

Thefutures 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 correspondingCoFuture class and itsCoPromise subclass.

//wraps some async func with CoFuturefunc makeIntFuture()->CoFuture<Int>{letpromise=CoPromise<Int>()someAsyncFunc{ intin        promise.success(int)}return promise}

It allows to start multiple tasks in parallel and synchronize them later withawait().

//create CoFuture<Int> that takes 2 sec. from the example aboveletfuture1:CoFuture<Int>=makeIntFuture()//execute coroutine on the global queue and returns CoFuture<Int> with future resultletfuture2:CoFuture<Int>=DispatchQueue.global().coroutineFuture{tryCoroutine.delay(.seconds(3)) //some work that takes 3 sec.return6}//execute coroutine on the main threadDispatchQueue.main.startCoroutine{letsum:Int=try future1.await()+ future2.await() //will await for 3 sec.self.label.text="Sum is\(sum)"}

It's very easy to transform or composeCoFutures into a new one.

letarray:[CoFuture<Int>]//create new CoFuture<Int> with sum of future resultsletsum=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 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 theCoChannel class.

//create a channel with a buffer which can store only one elementletchannel=CoChannel<Int>(capacity:1)DispatchQueue.global().startCoroutine{foriin0..<100{        //imitate some worktryCoroutine.delay(.seconds(1))        //sends a value to the channel and suspends coroutine if its buffer is fulltry 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 emptyforiin channel.makeIterator(){print("Receive", i)}print("Done")}

Scope

All launched coroutines,CoFutures andCoChannels, 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 andCoChannel 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,CoFutures,CoChannels and otherCoCancellable toCoScope to cancel them when they are no longer needed or on deinit.

classViewController:UIViewController{letscope=CoScope() //will cancel all objects on `cancel()` or deinitfunc performSomeWork(){        //create new `CoChannel` and add to `CoScope`letchannel=makeSomeChannel().added(to: scope)                //execute coroutine and add to `CoScope`DispatchQueue.main.startCoroutine(in: scope){[weak self]inforitemin channel.makeIterator(){tryself?.performSomeWork(with: item)}}}func performSomeWork(with item:Item)throws{        //create new `CoFuture` and add to `CoScope`letfuture=makeSomeFuture(item).added(to: scope)letresult=try future.await()... dosomework using result...}}

About

Swift coroutines for iOS, macOS and Linux.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift98.3%
  • C1.2%
  • Other0.5%

[8]ページ先頭

©2009-2025 Movatter.jp