AsyncNinja
  • March 15, 2024

A complete set of primitives for concurrency and reactive programming on Swift


  • 1.4.0 is the latest and greatest, but only for Swift 4.2 and 5.0
  • use 1.3.0 is for Swift 4.0+
  • use 1.2.4 for latest release for Swift 3
Features
🦄
powerful primitives
FuturePromiseChannelProducerSinkCache, …
🤘
versatile transformations
mapfilterrecoverdebouncedistinct, …
✌️
convenient combination
flatMapmergezipsamplescanreduce, …
🙌
improves existing things
Key-Value Observing, target-action, notifications, bindings
🍳
less boilerplate code
neat cancellation, threading, memory manament
🕶
extendable
powerful extensions for URLSession, UI controls, CoreData, …
🍱
all platforms
🖥 macOS 10.10+ 📱 iOS 8.0+ 📺 tvOS 9.0+ ⌚️ watchOS 2.0+ 🐧 Linux
🤓
documentation
100% + sample code, see full documentation
🔩
simple integration
SPMCocoaPodsCarthage

Communication


Reactive Programming


reactive properties

let searchResults = searchBar.rp.text
.debounce(interval: 0.3)
.distinct()
.flatMap(behavior: .keepLatestTransform) { (query) -> Future<[SearchResult]> in
return query.isEmpty
? .just([])
: searchGitHub(query: query).recover([])
}

bindings

  • unbinds automatically
  • dispatches to a correct queue automatically
  • no .observeOn(MainScheduler.instance) and .disposed(by: disposeBag)

class MyViewController: UIViewController {
/* … */
@IBOutlet weak var myLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
UIDevice.current.rp.orientation
.map { $0.description }
.bind(myLabel.rp.text)
}

/* … */
}

contexts usage

  • no [weak self]
  • no DispatchQueue.main.async { ... }
  • no .observeOn(MainScheduler.instance)

class MyViewController: NSViewController {
let service: MyService

/* … */

func fetchAndPresentItems(for request: Request) {
service.perform(request: request)
.map(context: self, executor: .primary) { (self, response) in
return self.items(from: response)
}
.onSuccess(context: self) { (self, items) in
self.present(items: items)
}
.onFailure(context: self) { (self, error) in
self.present(error: error)
}
}

func items(from response: Response) throws -> [Items] {
/* … extract items from response … */
}

func present(items: [Items]) {
/* … update UI … */
}
}

class MyService {
func perform(request: Request) -> Future<Response> {
/* … */
}
}

In Depth


Let’s assume that we have:

  • Person is an example of a struct that contains information about the person.
  • MyService is an example of a class that serves as an entry point to the model. Works in a background.
  • MyViewController is an example of a class that manages UI-related instances. Works on the main queue.

Code on callbacks

extension MyViewController {
func present(personWithID identifier: String) {
myService.fetch(personWithID: identifier) {
(person, error) in

/* do not forget to dispatch to the main queue */
DispatchQueue.main.async {

/* do not forget the [weak self] */
[weak self] in
guard let strongSelf = self
else { return }

if let person = person {
strongSelf.present(person: person)
} else if let error = error {
strongSelf.present(error: error)
} else {
fatalError(“There is neither person nor error. What has happened to this world?”)
}
}
}
}
}

extension MyService {
func fetch(personWithID: String, callback: @escaping (Person?, Error?) -> Void) {
/* … */
}
}

  • “do not forget” comment x2
  • the block will be retained and called even if MyViewController was already deallocated

Code with other libraries that provide futures

extension MyViewController {
func present(personWithID identifier: String) {
myService.fetch(personWithID: identifier)

/* do not forget to dispatch to the main queue */
.onComplete(executor: .main) {

/* do not forget the [weak self] */
[weak self] (completion) in
if let strongSelf = self {
completion.onSuccess(strongSelf.present(person:))
completion.onFailure(strongSelf.present(error:))
}
}
}
}

extension MyService {
func fetch(personWithID: String) -> Future<Person> {
/* … */
}
}

  • “do not forget” comment x2
  • the block will be retained and called even if MyViewController was already deallocated

Code with AsyncNinja

extension MyViewController {
func present(personWithID identifier: String) {
myService.fetch(personWithID: identifier)
.onSuccess(context: self) { (self, person) in
self.present(person: person)
}
.onFailure(context: self) { (self, error) in
self.present(error: error)
}
}
}

extension MyService {
func fetch(personWithID: String) -> Future<Person> {
/* … */
}
}

Using Futures


Let’s assume that we have function that finds all prime numbers lesser than n

func primeNumbers(to n: Int) -> [Int] { /* … */ }

Making future

let futurePrimeNumbers: Future<[Int]> = future { primeNumbers(to: 10_000_000) }

Applying transformation

let futureSquaredPrimeNumbers = futurePrimeNumbers
.map { (primeNumbers) -> [Int] in
return primeNumbers.map { (number) -> Int
return number * number
}
}

Synchronously waiting for completion

if let fallibleNumbers = futurePrimeNumbers.wait(seconds: 1.0) {
print(“Number of prime numbers is \(fallibleNumbers.success?.count)”)
} else {
print(“Did not calculate prime numbers yet”)
}

Subscribing for completion

futurePrimeNumbers.onComplete { (falliblePrimeNumbers) in
print(“Number of prime numbers is \(falliblePrimeNumbers.success?.count)”)
}

Combining futures

let futureA: Future<A> = /* … */
let futureB: Future<B> = /* … */
let futureC: Future<C> = /* … */
let futureABC: Future<(A, B, C)> = zip(futureA, futureB, futureC)

Transition from callbacks-based flow to futures-based flow:

class MyService {
/* implementation */

func fetchPerson(withID personID: Person.Identifier) -> Future<Person> {
let promise = Promise<Person>()
self.fetchPerson(withID: personID, callback: promise.complete)
return promise
}
}

Transition from futures-based flow to callbacks-based flow

class MyService {
/* implementation */

func fetchPerson(withID personID: Person.Identifier,
callback: @escaping (Fallible<Person>) -> Void) {
self.fetchPerson(withID: personID)
.onComplete(callback)
}
}

Using Channels


Let’s assume we have function that returns channel of prime numbers: sends prime numbers as finds them and sends number of found numbers as completion

func makeChannelOfPrimeNumbers(to n: Int) -> Channel<Int, Int> { /* … */ }

Applying transformation

let channelOfSquaredPrimeNumbers = channelOfPrimeNumbers
.map { (number) -> Int in
return number * number
}

Synchronously iterating over update values.

for number in channelOfPrimeNumbers {
print(number)
}

Synchronously waiting for completion

if let fallibleNumberOfPrimes = channelOfPrimeNumbers.wait(seconds: 1.0) {
print(“Number of prime numbers is \(fallibleNumberOfPrimes.success)”)
} else {
print(“Did not calculate prime numbers yet”)
}

Synchronously waiting for completion #2

let (primeNumbers, numberOfPrimeNumbers) = channelOfPrimeNumbers.waitForAll()

Subscribing for update

channelOfPrimeNumbers.onUpdate { print(“Update: \($0)”) }

Subscribing for completion

channelOfPrimeNumbers.onComplete { print(“Completed: \($0)”) }

Making Channel

func makeChannelOfPrimeNumbers(to n: Int) -> Channel<Int, Int> {
return channel { (update) -> Int in
var numberOfPrimeNumbers = 0
var isPrime = Array(repeating: true, count: n)

for number in 2..<n where isPrime[number] {
numberOfPrimeNumbers += 1
update(number)

// updating seive
var seiveNumber = number + number
while seiveNumber < n {
isPrime[seiveNumber] = false
seiveNumber += number
}
}

return numberOfPrimeNumbers
}
}

GitHub


View Github

#async #channel #concurrency #concurrency-library #functional #future #reactive #swift
YOU MIGHT ALSO LIKE...
CameraBackground

Features Both front and back camera supported. Flash modes: auto, on, off. Countdown timer. Tap to focus. Pinch to zoom. Usage  

DKCamera

Description A light weight & simple & easy camera for iOS by Swift. It uses CoreMotion framework to detect device orientation, so ...

HorizonSDK-iOS

Horizon SDK is a state of the art real-time video recording / photo shooting iOS library. Some of the features ...

LLSimpleCamera

LLSimpleCamera: A simple customizable camera - video recorder control LLSimpleCamera is a library for creating a customized camera - video ...

RSBarcodes_Swift

RSBarcodes allows you to read 1D and 2D barcodes using the metadata scanning capabilities introduced with iOS 7 and generate ...