Swiftpack.co - Package - glessard/deferred

Deferred Build Status

A lock-free, asynchronous Result.

Deferred<T> allows you to chain closures together. A Deferred starts with an indeterminate, unresolved value. At some later time it may become resolved. Its value is then immutable for as long as that particular Deferred instance exists. Until a Deferred becomes resolved, computations that depend on it can be saved for future execution using a lock-free, thread-safe algorithm. The results of these computations are also represented by Deferred instances. Errors thrown at any point in a Deferred context are propagated effortlessly.

Deferred started out as an approximation of OCaml's module Deferred.

let d = Deferred<Double> {
  usleep(50000) // or a long calculation
  return 1.0
}
print(d.value!) // 1.0, after 50 milliseconds

A Deferred can schedule code that depends on its result for future execution using the notify, map, flatMap and apply methods. Any number of such blocks can depend on the result of a Deferred. When an Error is thrown, it is propagated to all Deferreds that depend on it. A recover step can be inserted in a chain of transforms in order to handle potential errors.

let transform = Deferred { i throws in Double(7*i) } // Deferred<Int throws -> Double>
let operand = Deferred(value: 6).delay(seconds: 0.1) // Deferred<Int>
let result = operand.apply(transform: transform)     // Deferred<Double>
print(result.value!)                                 // 42.0

The result property of Deferred (and its adjuncts, value , error and get()) will block the current thread until its Deferred becomes resolved. The rest of Deferred is lock-free.

Deferred can run its closure on a specified DispatchQueue, or at the requested DispatchQoS. The notify, map, flatMap, apply and recover methods also have these options. By default, closures will be scheduled on a queue created at the current quality-of-service (qos) class.

Long computations, cancellations and timeouts

Long background computations that support cancellation and timeout can be implemented easily by using the TBD<T> subclass of Deferred<T>. Its constructor takes a closure whose parameter is a Resolver. Resolver allows your code be a data source of Deferred it's associated with, as well as monitor the state of its Deferred.

func bigComputation() -> Deferred<Double>
{
  return TBD<Double> {
    resolver in
    DispatchQueue.global(qos: .utility).async {
      var progress = 0
      repeat {
        guard resolver.needsResolution else { return }
        Thread.sleep(until: Date() + 0.01) // work hard
        print(".", terminator: "")
        progress += 1
      } while progress < 20
      resolver.resolve(value: .pi)
    }
  }
}

let validated = bigComputation().validate(predicate: { $0 > 3.14159 && $0 < 3.14160 })
let timeout = 0.1
validated.timeout(seconds: timeout, reason: String(timeout))

do {
  let pi = try validated.get()
  print(" ", pi)
}
catch DeferredError.timedOut(let message) {
  print()
  XCTAssertEqual(message, String(timeout))
}

In the above example, our code works hard to compute the ratio of a circle's circumference to its radius, then performs a rough validation of the output by comparing it with a known approximation. Finally, the Note that the Deferred object performing the computation is not retained directly by this user code. Yet, when the timeout is triggered the computation is correctly abandoned and the object is deallocated. General cancellation can be performed in a similar manner.

Deferred is carefully written to support cancellation by leveraging reference counting. In every case, when a new Deferred is returned by a function from the package, that returned reference is the only strong reference in existence. This allows cancellation to work properly for entire chains of transformation, even if it is applied to the final link of the chain.

Using Deferred in a project

With the swift package manager, add the following to your package manifest's dependencies:

.package(url: "https://github.com/glessard/deferred.git", from: "5.0.0")

To integrate in an Xcode project, tools such as Accio and xspm are good options. The repository contains a project with an example iOS target that is manually assembled. It requires some git submodules to be loaded using the command git submodule update --init.

License

This library is released under the MIT License. See LICENSE for details.

Github

link
Stars: 3
Help us keep the lights on

Used By

Total:

Releases

5.1.0 - May 23, 2019

Encapsulation improvements and improved lock-freedom.

5.0.0 - May 19, 2019

5.0.0-b2 - May 16, 2019

4.4.0 - Feb 9, 2019

Improves performance by consolidating notifications and deallocations

  • fewer calls to dispatch_async
  • more work done in each dispatch_async

Improvements to and full testing of DeferredURLSessionTask.

Added a split method for any Deferred whose value type is a tuple of 2, 3 or 4 elements. The return value is a tuple of Deferred; if the original Deferred's outcome is an Error, then every Deferred in the split is determined with the same Error.

4.3.0 - Oct 20, 2018

  • added parameter labels to determine() methods (with fix-its.) The parameter-less versions didn't save anything much, while causing grief when Value has a type that implements Error

  • improved behaviour of firstValue(). Its outcome is now an error only when all the inputs are errors.

  • the old behaviour is still provided by firstDetermined().