DeLorean 0.4.1

DeLorean 0.4.1

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Apr 2015
SPMSupports SPM

Maintained by Junior B..



DeLorean 0.4.1

  • By
  • bontoJR

DeLorean

DeLorean

DeLorean is a Functional Reactive Kit for Swift.

// Currently not supporting Swift 1.2

Abstract

DeLorean is a framework (still WIP) focused in delivery a complete implementation of some fundamentals of asynchronous programming in a functional style for Swift. The framework wants to cover the following 2 big concepts:

  • Futures and Promises (called Flux Capacitor module)
  • Reactive Programming (called Mr. Fusion module)

Motivation

Swift is gaining traction as Functional Programming language, generating a lot of debates. This framework has been created to implement two big concepts in a functional style: Parallel Non-blocking Operations (Futures/Promises) and Reactive Programming (Streams).

The reason why this two different worlds can work together is because Future and Promise are concepts that perfectly fit definite operations, like a HTTP request or a JSON deserialization. On the other hand, Reactive Programming fits perfectly to handle operations with random inputs, like UI interactions (f.e: TextField input), location manager updates, external accessories interactions, websockets, etc…

Contribute

You can freely contribute to the development of DeLorean, please checkout the repository, add the features you would like to have and create a pull-request. This repository uses the GitFlow Workflow, please check it if you don’t know how it works before sending a pull-request. Pull-requests to master branch are going to be ignored (if not for an extremely urgent hotfix). Please consider to create a dedicated test for the feature, so future updates can avoid to break current-working code.

What’s behind this framework?

I strongly recommend you to read the following resources to understand what inspired this framework:

or check these online courses:

Status

The framework is in development and the current status will be updated regularly. The current development status is:

Flux Capacitor (Futures and Promises Module)

Flux Capacitor has been completed and includes all major operations to deal with Promises and Futures like: flatMap, zip, then, fallbackTo, etc…

Mr. Fusion (Reactive Module)

Mr. Fusion development is in early stage, please don’t use this module right now.

How to use it

Installation

Support for Carthage and Cocoapods have not been tested or included, yet.

Flux Capacitor

The concept is very simple, a Future is a sort of a placeholder object that you can create for a result that has to be currently computed. Futures provide a nice way to perform many operations in parallel, using an efficient and non-blocking way. By default, the result of the Future is computed concurrently and can be later collected. Composing tasks in this way tends to result in safer, asynchronous, non-blocking parallel code. While Futures are read-only placeholders, Promises are an extension of Futures that can be also written.

Note: This implementation is Scala-complaint, so the Scala’s documentation about Future and Promises is actually also valid.

Futures

A Future is an object holding a result which may become available at some point. The result is usually the achieved with some other computation:

  • If the computation has not yet completed, we say that the Future is not completed.
  • If the computation has completed with a value or with an exception, we say that the Future is completed.

So basically, asking for Future’s computation result can have the following scenarios:

  • The returned value will be nil, if the computation is not completed.
  • The value will be Value(t), in case it contains a valid result and is completed
  • The value will be Error(e) if it contains an error

Once the result has been computed, all registered callbacks are fired.

Example

The basic example with Future is the download of a file, with parse and display.

future {
    var response: NSURLResponse?
    var error : NSError? = nil
    if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) {
        return Result(data)
    }
    return Result(error!)
}.onSuccess(){ (result: NSData) in
    //Function parseDataAndUpdateUI just parse the data and basically displays the result
    self.parseDataAndUpdateUI(result)
    return
}.onFailure(){ (e: NSError) in
    //Function displayError just displays the error
    self.displayError(e)
    return
}

A Future can also be used to compute Void blocks asynchronously:

futureVoid {
    self.performLongTask() // defined as: func performLongTask() {...}
}
Why Returning a Wrapped Result?

The Scala/Java implementation of Future and Promise takes advantage of the Try/Catch/Finally statement, relying on the catch of Throwable (this includes Exception and Error). In Swift we don’t have the Try/Catch instruction and Apple has clearly expressed that it will not be included. This language design choice forced this implementation to require the Result structure to be returned as value at the end of the computational block. The first example shows how is possible to pass the error returned by a failed request or the data as result of a successful one.

Promises

Futures are objects created by asynchronous computations started using the future method. Promises are an extension of futures and futures can be created using this new type.

Futures are defined as read-only placeholders, but sometimes we need to be able to write in those placeholders. Promises have been created to achieve this behavior. A promise can be fulfilled only once, that means that we can either success or fail to fulfill a promise a single time, any further call to success or failure will be ignored.

Example

Consider the following example, in which a value is produced by a computation and is handed off to another computation which requires that value to be consumed. This process to pass a value is achieved using a Promise.

let stringURL = "http://rate-exchange.appspot.com/currency?from=USD&to=EUR"

var response: NSURLResponse?
var error : NSError? = nil
let contentRequest = NSURLRequest(URL: NSURL(string: stringURL)!)

let p = Promise<NSData>()
let f = p.future()

futureVoid {
    if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) {
        p.success(data)
    } else {
        p.failure(error!)
    }
}

f.onFailure(){ (e: NSError) in
    //Function displayError just displays the error
    self.displayError(e)
}

f.onSuccess{ data in
    //Process the data and eventually display it
    self.displayData(data)
}

Combining Futures

The library comes with many way to combine futures, here are some examples:

Chaining

With the then function we can chain futures:

// Example response:
// {"to": "CHF", "rate": 0.96462400000000004, "from": "USD"}
let stringURL = "http://rate-exchange.appspot.com/currency?from=USD&to=CHF"

future {
    var response: NSURLResponse?
    var error : NSError? = nil
    let contentRequest = NSURLRequest(URL: NSURL(string: stringURL)!)
    if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) {
        return Result(data)
    }
    return Result(error!)
}.then { (data: NSData) in
    var error : NSError? = nil
    if let dict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as! Dictionary<String, AnyObject>? {
        return Result(dict)
    }
    return Result(error!)
}.onSuccess(){ (result: Dictionary<String, AnyObject>) in
    self.processData(result)
}.onFailure(){ error in
    self.handleError(e)
}

FlatMap

Fmap creates a new Future by applying a function to the successful result of this future, returning a new future as the result of the function. If this future is completed with an error, the new one will contain the same error.


let f = future {
    return Result(fibonacci(35)) //returns 9'227'465
}.fmap{ value in
    return future {
        return Result(value + fibonacci(35)) //returns 9'227'465
    }
}

f.onSuccess() { value in
    // value is 18'454'930 (9'227'465 * 2)
}

Zip

Zip function zips the values of this and that future, and creates a new future holding the tuple of their results.

If this future fails, the resulting future is failed with the same error of this. Otherwise, if that future fails, the resulting future is failed with the error stored in that.

WARNING! The current implementation of Swift, doesn’t work with a plain generics tuples. To workaround this, we are using an array to box the current result tuple. As soon as Swift will correctly handle generics and tuples, we are going to change this method with a better implementation.

// Example response:
// {"to": "CHF", "rate": 0.96462400000000004, "from": "USD"}
let usdToChfURL = "http://rate-exchange.appspot.com/currency?from=USD&to=CHF"
let chfToEurURL = "http://rate-exchange.appspot.com/currency?from=CHF&to=EUR"

let usdToChf : Future<NSData> = future {
    var response: NSURLResponse?
    var error : NSError? = nil
    let contentRequest = NSURLRequest(URL: NSURL(string: usdToChfURL)!)
    if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) {
        return Result<NSData>(data)
    }
    return Result(error!)
}

let chfToEur : Future<NSData> = future {
    var response: NSURLResponse?
    var error : NSError? = nil
    let contentRequest = NSURLRequest(URL: NSURL(string: chfToEurURL)!)
    if let data = NSURLConnection.sendSynchronousRequest(contentRequest, returningResponse: &response, error: &error) {
        return Result<NSData>(data)
    }
    return Result(error!)
}

let zipped = usdToChf.zip(that: chfToEur)

zipped.onSuccess { value in
    let usdToChfData = value[0].0
    let chfToEurData = value[0].1
    // Process both values
}

and more…

Please consider to read the in-code documentation for map, filter, recover, recoverWith and fallbackTo.

Mr. Fusion

Mr. Fusion is still in a very early development stage. This module is the Reactive Core of DeLorean, if you are looking for an implementation similar to what Rx extensions are providing, you have probably found it.

Note: The decision to not keep the names like Observer, Observable, etc… has been made after a couple of hours of toughts. I don’t believe they fit the Cocoa world so well. Feedbacks about it are highly appreciated.

Emitter (Producer)

An Emitter is an entity that is emitting events. An event can be of any kind, from a simple Int to a complex object, passing through UI events. An emitter can be, for example, used to keep track of the user location, producing coordinates of the current position or it can be used to react on inputs by user on a button, slider or textfield.

To create an Emitter of type Int you can just use the simple init:

let emitter = Emitter<Int>()

You can then emit values, complete the emitter or make it fail.

emitter.emit(5)

// Error requires an instance of NSError
emitter.error(e)

emitter.complete()

Emitters are composable, it means we can merge, zip, concatenate, etc… two or more emitters, generating new emitters.

Subscription (Consumer)

A Subscription is a generalized mechanism to process push-based notification. In other resources it is also known as the observer. A Subscription is an object that represents a processor of notifications or events (also know as consumer).

To subscribe, the best way is to call the subscribe method on an Emitter.

let subscription = Subscription<Int>()
emitter.subscribe(subscription)

Another way is to request an empty subscription from an Emitter

let subscription = emitter.subscription()

In iOS, subclass of UIControl have a default Emitter object that fires objects of type ControlEvent. A ControlEvent requires a key, as instance of UIControlEvents and a value, that can be an Int, a Double, a String, a Float or a NSDate.

Here an example using a text field that fires a search only with strings longer than 3 characters:

// e is an instance of UIControlEvents<String>
searchTextField?.emitter.filter({ e in
    return count(e.value) > 3
}).subscription().onNext() { event in
    self.search(term: event.value)
}

We can also use the same process to filter over UIButtons:

searchBtn.emitter.filter({e in return e.key == .TouchUpInside}).subscription().onNext(){ _ in
    self.search(term: searchTextField?.text)
}

Scheduler

A Scheduler provides a set of methods to access commonly used Execution Contexts and acts as a base class for all schedulers.

Predefined schedulers are:

/// The scheduler where all UI events are performed
public static let UIScheduler = Scheduler(ExecutionContext.mainContext)

/// The default scheduler
public static let Default = Scheduler()

/// The background scheduler for very time consuming producers
public static let BackgroundScheduler = Scheduler(ExecutionContext.concurrentContext)

Note: UI Scheduler schedules on the main queue.

External Libraries

Implementations of Result.swift and Operators.swift are originally made by Maxwell Swadling and are part of the Swiftz functional library.

LICENSE

The MIT License (MIT)

Copyright © 2015 Junior B. (bonto.ch)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.