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

Overview

AsyncNinja Title

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

Gitter CocoaPods Carthage compatible Build Status

  • 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
Future, Promise, Channel, Producer, Sink, Cache, ...
๐Ÿค˜
versatile transformations
map, filter, recover, debounce, distinct, ...
โœŒ๏ธ
convenient combination
flatMap, merge, zip, sample, scan, reduce, ...
๐Ÿ™Œ
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
SPM, CocoaPods, Carthage

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 {
    /* ... */
  }
}

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

Void) { /* ... */ } } ">
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 {
    /* ... */
  }
}
  • "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 {
    /* ... */
  }
}

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

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

class MyService {
  /* implementation */
  
  func fetchPerson(withID personID: Person.Identifier) -> Future {
    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) -> 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
  }
}
Comments
  • Swift 4 exclusive memory access enforcement

    Swift 4 exclusive memory access enforcement

    Hi!

    As you know, Swift 4 introduces Exclusive Memory Access Enforcement, which gives us some kinds of issues when using SpinLockLocking and UnfairLockLocking structs under Locking protocol. Is there a reason to use struct for these types? Or it may be changed to class to avoid Thread Sanitizer errors? Thanks!

    screen shot 2018-06-18 at 12 44 01 pm screen shot 2018-06-18 at 12 50 49 pm

    opened by nezhyborets 3
  • what the freak?!@?!#!?#

    what the freak?!@?!#!?#

    maybe i'm too hyper but on first glance this looks like the most complete usable async reactive library for swift/ios looks wise and feel wise without even touching the code!!!!

    thank you !!

    opened by sirvon 3
  • producer and promise factories UPD + combineLatest

    producer and promise factories UPD + combineLatest

    UPD. This pull request was updated I decided to rename my previous channel makers into producer makers. I believe it can simplify its usage. Also I added promise makers

    Screen Shot 2019-03-28 at 8 17 58 PM

    Screen Shot 2019-03-28 at 8 25 39 PM

    UPDATE:

    1. promise and future factories are now extension methods of ExecutionContext, so there is no more necessity to pass context as argument
    2. combineLatest (for 2,3 and 4 EventSources) implemented as extension methods of ExecutionContext + Unit Tests
    opened by serg-vinnie 2
  • Set Product Bundle Identifier to name with a dot

    Set Product Bundle Identifier to name with a dot

    Screen Shot 2019-10-08 at 10 45 11 PM

    To fix this, I had to set framework's Product Bundle Identifier to anything with a dot.

    For some reason Xcode would automatically append some autogenerated ID (5555494493d11a8e5f473d1cb2a5d781973d171e) when Product Bundle Identifier was just "AsyncNinja".

    When I set Product Bundle Identifier to new value, Xcode stopped to auto-append anything, and everything works because ids match. Probably absence of a dot was the reason.

    opened by nezhyborets 1
  • Analog of signal

    Analog of signal

    Which of primitives in your project I can use for simple signal like in ReactiveCocoa? I want to just send events in one place and receive them in another place.

    opened by krupin-maxim 1
  • New Features

    New Features

    1. combine<C1:Completing, C2:Completing>(_ c1: C1, _ c2: C2) + test
    2. assign(to keyPath:, on obj:) - bind updates to keyPath of any Ratainer
    3. @WrappedProducer โ€“ AsyncNinja analogue for @Published
    opened by serg-vinnie 0
  • CRITICAL: completion can overcome update on Executor.default

    CRITICAL: completion can overcome update on Executor.default

    there is major problem related to serial executors: completion can overcome update. Lets see an example: as you can see completion goes just after update

    public extension CKContainer {
        func userRecordID() -> Channel<CKRecord.ID, Void> {
            return producer() { [weak self] producer in
                self?.fetchUserRecordID { recordID, error in
                    if let error = error { producer.fail(error) }
                    if let id = recordID {
                        producer.update(id)
                        //sleep(1)
                        producer.succeed()
                    }
                }
            }
        }
    }
    

    subscription to such channel usually works as expected, but nested map structure doesn't:

    cloud.userRecordID()
        .map { "prefix" + $0.recordName }    // update never (usually) come here, only completion
        .map { CKRecord.ID(recordName: $0) } // same shit
    

    there are different ways to bypass this problem: sleep before completion and to use serial executor. But both of them are dirty hacks.

    Serial executor can look like worthy solution, but it can make a deadlock while flatMapping. This is actually another issue I want to report (log message with warning about deadlock can be acceptable solution).

    I'm ready to take effort and spend my own time for fixing these bugs, but any help will be highly appreciated.

    opened by serg-vinnie 0
  • flatMap deadlock on serial Executor

    flatMap deadlock on serial Executor

    to reproduce deadlock just flatMap any channel using same serial Executor for flatMapping and updating channel.

    someChannel.
        flatMap(context: self) { me, value in me.transformToChannel(value) }
    

    Reason of deadlock:

    1. flatMap uses semaphore to wait for update
    2. channel unable to send update to flatMap handler bcs thread is blocked by semaphore

    Screenshot 2019-12-07 at 11 08 29

    P.S. There are two ways to avoid dead lock

    1. concurrent Executor for flatMaping. But it ruins updates order and often Completion can get ahead of some Updates
    2. separate serial Executors for channel and flatMap can become painful for nested flatMaps.
    opened by serg-vinnie 0
Releases(1.4.0)
  • 1.4.0(Mar 5, 2019)

    • [improved] support of Swift 5.0
    • [changed] Fallible type is a typealias to Result for Swift 5.0
    • [added] Channel convenience constructors
    • [added] a few reactive properties for AppKit
    • [fixed] DynamicProperty storage synchronization
    • [fixed] bindings memory issue
    Source code(tar.gz)
    Source code(zip)
  • 1.3.2(Dec 30, 2018)

    • [changed] default Swift version has been updated to 4.2. 4.0-4.1 is dropped due to changes in UIKit enums naming
    • [improvement] tested against latest Swift 5.0 build. No errors, no warnings, tests are passing
    • [improvement] annoying warnings fixed
    • [improvement] minor improvements of documentation
    Source code(tar.gz)
    Source code(zip)
  • 1.3.1(Jun 9, 2018)

    • [fixed] nested executes using the same executor Executor could be optimized into on-stack execution instead of dispatch_async call. That is a direct cause of stack overflow. Avoiding that by limiting amount of nested on-stack executions.
    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Feb 25, 2018)

  • 1.2.4(Nov 18, 2017)

  • 1.2.3(Oct 22, 2017)

  • 1.2.2(Sep 30, 2017)

  • 1.2.1(Sep 3, 2017)

  • 1.2.0(Aug 21, 2017)

    • [changed] lock-free implementation of Future and CancellationToken changed to regular locking-based
    • [fixed] multiple Xcode 9.0 beta 5 compile time issues
    Source code(tar.gz)
    Source code(zip)
  • 1.1.3(Jun 1, 2017)

    • [added] documentation generation infrastructure
    • [added] primitive subscription chaining (future.onSuccess { /* handle success */ }.onFailure { /* handle failure */ })
    • [improved] parallel CI jobs
    • [improved] Promise internals updated
    • [fixed] warnings and erros introduced by Swift 4 toolchain
    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(May 8, 2017)

  • 1.1.1(Apr 15, 2017)

    • [added] network reachability
    • [improved] travis-ci config
    • [fixed] potential leak of handlers in Promise
    • [fixed] EventSource.first
    • [fixed] EventSource.last
    • [fixed] multiple flaky unit tests
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Apr 9, 2017)

    • try execute methods added. They provide an ability to retry async execution multiple times
    • convenience initializers for Executor added alongside with constructors to reduce confusion in some cases
    • EventSource.skip implemented
    • EventSource.take implemented
    • EventSource.contains implemented
    • EventSource.suspendable implemented
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Apr 2, 2017)

  • 1.0.0-beta7(Mar 26, 2017)

    • staticCast for Future and Channel added
    • future to channel and channel to future transformations added
    • Cache and CachableValue convenience initializers improved
    • fixing major issue with asyncMap(), asyncFlatMap(), asyncReduce(), joined()
    • fixing issue with inconsistent lifetime of the Future returned from contextual transformation of Completing
    • adding ability to denote transformation as impure an apply different lifetime management for transformations
    • documentation improved
    • TimerChannel refactored to improve quality of implementation and to add flexibility
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta6(Mar 19, 2017)

    • documentation updated
    • Cache and CachedValue improved
    • URLSession extensions updated
    • Future.flatMap improved
    • Channel.extractAll() refactored to provide Future<(updates: [Update], completion: Fallible<Success>)>
    • Channel.waitForAll() refactored and extended
    • Major fixes of Cache and CachableValue
    • Carthage support
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta5(Mar 12, 2017)

    • adding ability to nest synchronously calls on the same executor instead of making async dispatches all the time
    • naming improved
    • infrastructure build up on protocols has been build
    • major reactive programming improvements: ReactiveProperties, convenience extensions added to most popular iOS controls, bindings added
    • Sink, DynamicProperty, ProducerProxy added
    • fixing and improving memory management/lifetime management of Channel
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta3(Feb 20, 2017)

    • improvements using OptionalAdaptor
    • fixed locking within CachableValue
    • fixes of ObjCInjectedRetainer
    • adding unwrapping methods to Future and Channel
    • making more obvious behavior of Channel.flatMap a default one
    • introducing ObservationSession that helps to enable and disable multiple observations in a convenient way. This is very useful for enabling and disabling observation within viewWillAppear and viewDidDisappear
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta2(Feb 18, 2017)

    • continue renaming
    • adding convenience methods for making completed futures and channels
    • adding convenience typealias Updating<T> = Channel<T, Void>, typealias Updatable<>T = Producer<T, Void>
    • adding OptionalAdaptor that helps to add methods for primitives parametrized with optionals
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta(Feb 15, 2017)

  • 0.4.5(Feb 12, 2017)

    • adding 5 various behaviors to flatMap that transforms periodic to future
    • adding descriptions (CustomStringConvertible, CustomDebugStringConvertible) for Channel, Future, Fallible, Either
    • introducing multirun tests in order to fix flaky tests
    • Channel is now Sequence
    • fixes of Channel
    Source code(tar.gz)
    Source code(zip)
  • 0.4.4(Feb 7, 2017)

  • 0.4.3(Jan 22, 2017)

    • fixing code style
    • adding reduce(::) method to channel
    • convenience method for leaving group
    • fixing asyncMaps and improving tests
    • fixing compatibility with current swift master
    Source code(tar.gz)
    Source code(zip)
  • 0.4.2(Jan 4, 2017)

    • taking advantage of ability to make derived serial executor in order to avoid using locks
    • operations on channels added: first(where:), last(where:), zip(_:, _:)
    • fixing rare issue that could cause early releases of instances that depend on futures and channels
    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Jan 2, 2017)

  • 0.4.0(Jan 2, 2017)

  • 0.4.0-beta4(Jan 1, 2017)

  • 0.4.0-beta3(Dec 31, 2016)

    • ActionChannels to NSControl and UIControl that catch actions and treat them as periodic values
    • turning down performance tests
    • fixing critical issues of lifetime of Futures and Channels
    Source code(tar.gz)
    Source code(zip)
  • 0.4-beta1(Dec 30, 2016)

    • AsyncNinja was split into multiple modules
    • previously deprecated Pipe was removed: use Channel with iterators instead
    • making key-value observing channel added
    Source code(tar.gz)
    Source code(zip)
  • 0.3.7(Dec 28, 2016)

Owner
AsyncNinja
AsyncNinja toolset and related content
AsyncNinja
Functional Concurrency Primitives

Concurrent Concurrent is a collection of functional concurrency primitives inspired by Concurrent ML and Concurrent Haskell. Traditional approaches to

TypeLift 206 Dec 24, 2022
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
Slack message generator and API client, written in Swift with Result Builders and Concurrency

Slack Message Client This package provides a Swift object model for a Slack Block Kit message, as well as a Result Builder convenience interface for e

Mike Lewis 2 Jul 30, 2022
A Swift DSL that allows concise and effective concurrency manipulation

NOTE Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves man

Jason Fieldman 25 May 24, 2019
โšก๏ธ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Said Sikira 814 Oct 30, 2022
The projects and materials that accompany the Modern Concurrency in Swift book

Modern Concurrency in Swift: Materials This repo contains all the downloadable materials and projects associated with the Modern Concurrency in Swift

raywenderlich 137 Dec 16, 2022
Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6.

ConcurrencyCompatibility Tools for using Swift Concurrency on macOS 10.15 Catalina, iOS 13, tvOS 13, and watchOS 6. Xcode 13.2 adds backwards deployme

Zachary Waldowski 9 Jan 3, 2023
A Modern Concurrency and Synchronization for Swift.

##Features Simple Atomic<T> class for numbers and strings. Uncomplicated dispatch keyword for firing off background routines. Awesome Chan<T> for conc

Josh Baker 421 Jun 30, 2022
A declarative state management and dependency injection library for SwiftUI x Concurrency

A declarative state management and dependency injection library for SwiftUI x Concurrency

Ryo Aoyama 199 Jan 1, 2023
Swift concurrency collection support

AsyncCollections Functions for running async processes on Swift Collections ForEach Run an async function on every element of a Sequence. await array.

Adam Fowler 11 Jul 11, 2022
An introduction to using Swift's new concurrency features in SwiftUI

SwiftUI Concurrency Essentials An introduction to using Swift's new concurrency features in SwiftUI Discuss with me ยท Report Bug ยท Request Feature Art

Peter Friese 80 Dec 14, 2022
Type-safe networking with Swift Concurrency

AsyncRequest AsyncRequest is a type-safe framework for building a suite of requests to communicate with an API, built on top of Swift Concurrency. Ins

Light Year Software, LLC 1 Feb 9, 2022
AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency

AsyncOperators brings some features of RxSwift/Combine to Structured Concurrency, such as combineLatest and distinctUntilChanged.

Ben Pious 3 Jan 18, 2022
Egg-Timer - Intermediate Swift Programming - Control Flow and Optionals

Egg-Timer Intermediate Swift Programming - Control Flow and Optionals What I lea

null 0 Jan 10, 2022
Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift.

Futures Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift. It's lightweight, fast, and easy to understa

David Ask 60 Aug 11, 2022
Operation Oriented Programming in Swift

Flow Flow is a lightweight Swift library for doing operation oriented programming. It enables you to easily define your own, atomic operations, and al

John Sundell 218 Feb 6, 2022
Async and concurrent versions of Swiftโ€™s forEach, map, flatMap, and compactMap APIs.

CollectionConcurrencyKit Welcome to CollectionConcurrencyKit, a lightweight Swift package that adds asynchronous and concurrent versions of the standa

John Sundell 684 Jan 9, 2023
Automatically generate GraphQL queries and decode results into Swift objects, and also interact with arbitrary GitHub API endpoints

GitHub API and GraphQL Client This package provides a generic GitHub API client (GithubApiClient) as well as Codable-like GitHub GraphQL querying and

Mike Lewis 4 Aug 6, 2022
Make your logic flow and data flow clean and human readable

Flow What's Flow Flow is an utility/ design pattern that help developers to write simple and readable code. There are two main concerns: Flow of opera

null 18 Jun 17, 2022