Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/spring-media/PiedPiper

A small set of classes and functions to make easy use of Futures, Promises and async computation in general. All written in Swift for iOS 10+, WatchOS 3, tvOS and Mac OS X apps.
https://github.com/spring-media/PiedPiper

apps promise swift swift-package-manager

Last synced: about 1 month ago
JSON representation

A small set of classes and functions to make easy use of Futures, Promises and async computation in general. All written in Swift for iOS 10+, WatchOS 3, tvOS and Mac OS X apps.

Awesome Lists containing this project

README

        

# Pied Piper [Deprecated]

[![Build Status](https://travis-ci.org/spring-media/PiedPiper.svg?branch=master)](https://travis-ci.org/spring-media/PiedPiper)
[![Version](https://img.shields.io/cocoapods/v/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)
[![Platform](https://img.shields.io/cocoapods/p/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)

---
**NOTE**

PiedPiper is no longer supported. We suggest to use [Combine](https://developer.apple.com/documentation/combine)framework if you target iOS 13+ or [OpenCombine](https://github.com/OpenCombine/OpenCombine) if you target iOS 12 and lower.

---

> A small set of classes and functions to make easy use of `Future`s, `Promise`s and async computation in general. All written in Swift for `iOS 10`, `WatchOS 3`, `tvOS` and `Mac OS X` apps.

# Contents of this Readme

- [What is Pied Piper?](#what-is-pied-piper)
- [Installation](#installation)
- [Playground](#playground)
- [Requirements](#requirements)
- [Usage](#usage)
- [Usage examples](#usage-examples)
- [Futures](#futures)
- [Promises](#promises)
- [GCD Computation](#gcd-computation)
- [Advanced usage with Futures](#advanced-usage-with-futures)
- [Function composition](#function-composition)
- [Tests](#tests)
- [Future development](#future-development)
- [Apps using Pied Piper](#apps-using-pied-piper)
- [Authors](#authors)
- [License](#license)
- [Acknowledgements](#acknowledgements)

## What is Pied Piper?

`Pied Piper` is a small set of classes, functions and convenience operators to **write easy asynchronous code** in your application.

With `Pied Piper` you can:

- Use [`Future`](#futures)s and [`Promise`](#promises)s for your async tasks
- Write easy to read async code [on top of GCD](#gcd-computation)
- [Compose asynchronous functions](#function-composition) in an intuitive way

## Installation

### CocoaPods

`Pied Piper` is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:

```
pod "PiedPiper"
```

### Submodule

If you don't use CocoaPods, you can still add `Pied Piper` as a submodule, drag and drop `PiedPiperSample.xcodeproj` into your project, and embed `PiedPiper.framework` in your target.

- Drag `PiedPiperSample.xcodeproj` to your project
- Select your app target
- Click the `+` button on the `Embedded binaries` section
- Add `PiedPiper.framework`

### Carthage

`Carthage` is also supported.

### Manual

You can directly drag and drop the needed files into your project, but keep in mind that this way you won't be able to automatically get all the latest `Pied Piper` features (e.g. new files including new operations).

The files are contained in the `PiedPiper` folder and work for the `iOS`, `watchOS`, `MacOS` and `tvOS` frameworks.

## Playground

We ship a small Xcode Playground with the project, so you can quickly see how `Pied Piper` works and experiment with your custom layers, layers combinations and different configurations for requests pooling, capping, etc.

To use our Playground, please follow these steps:

- Open the Xcode project `PiedPiperSample.xcodeproj`
- Select the `Pied Piper` framework target, and a **64-bit platform** (e.g. `iPhone 6`)
- Build the target with `⌘+B`
- Click the Playground file `PiedPiper.playground`
- Write your code

## Requirements

- iOS 10.0+
- WatchOS 3+
- Mac OS X 10.12+
- tvOS 10+

## Usage

To run the example project, clone the repo.

### Usage examples

### Futures

A `Future` is an object representing a computation that may not have happened yet. You can add callback handlers for the success, failure, and cancelation events of a specific `Future`.

Please keep in mind that a `Future` is not a signal. It can only fail, succeed or cancel (mutually exclusive) and it can only do so once.

Please also note that a `Future` is "read-only". This means you can not actively determine the result of an instance. If you are implementing your own asynchronous computations, please have a look at [promises](#promises) instead.

```swift
// The login function returns a Future
let login = userManager.login(username, password)

// You can specify success or failure callbacks, and chain the calls together
login.onSuccess { user in
print("User \(user.username) logged in successfully!")
}.onFailure { error in
print("Error \(error) during login")
}.onCancel {
print("Login was cancelled by the user")
}

// Or you can use onCompletion instead
login.onCompletion { result in
switch result {
case .Success(let user):
print("User \(user.username) logged in successfully!")
case .Error(let error):
print("Error \(error) during login")
case .Cancelled:
print("Login was cancelled by the user")
}
}
```

Since `Pied Piper 0.8` you can use convenience initializers on `Future` if you already know the result without any asynchronous work:

```swift
let future = Future(10)

// or

let future = Future(MyError.SomeError)

// or

let future = Future(value: possiblyNil, error: MyError.SomeError)

// or

let future: Future = Future {
return asyncCodeThatFetchesImage()
}
```

### Promises

`Future`s are really handy when you are the user of some async computations. But sometimes you may want to be the producer of these, and in this case you need to be able to determine when a `Future` should succeed or fail. Then you need a `Promise`.

```swift
func login(username: String, password: String) -> Future {
// Promises, like Futures, are generic
let promise = Promise()

GCD.background {
//Request to the server...
//...
if response.statusCode == 200 {
// Success
promise.succeed(userFromResponse(response))
} else {
// Your ErrorType can be used as an argument to fail
promise.fail(LoginError.InvalidCredentials)
}
}

// If the user wants to cancel the login request
promise.onCancel {
//cancel the request to the server
//...
}

// we don't want users to be able to fail our request
return promise.future
}
```

### GCD computation

`Pied Piper` offers some helper functions on top of GCD to run blocks of code on the main queue or on a background queue.

```swift
//Without Pied Piper
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Execute your asynchronous code
// ...
let result = 10

// Notify on the main queue
dispatch_async(dispatch_get_main_queue()) {
print("Result is \(result)")
}
}

//With Pied Piper
GCD.background { Void -> Int in
// Execute your asynchronous code
return 10
}.main { result in
print("Result is \(result)")
}
```

You can also run your asynchronous code on serial queues or your own queues.

```swift
// Serial queue
let queue = GCD.serial("test")

// Your own queue
let queue = GCD(queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))

// Then...
queue.async { Void -> Int in
return 10
}.main {
print($0)
}
```

### Advanced usage with Futures

Since `Pied Piper 0.8` many convenience functions are available on `Future` values, like `map`, `flatMap`, `filter`, `recover`, `retry`, `zip`, `reduce`, `mergeSome` and `mergeAll`. Moreover, `traverse` is available for all `SequenceType` values.

Since `Pied Piper 0.9` some more functions are available like `snooze`, `timeout` and `firstCompleted` (the latter for a `SequenceType` of `Future` values).

Keep in mind that some of these functions (`map`, `flatMap` and `filter`) are also available on `Result` values. They work just like their `Future` counterparts.

#### FlatMap, Map, Filter

```swift
// In this snippet `doStuff` returns a Future
let newFuture = doStuff().filter { value in
value > 0
}.flatMap { value in
Future("\(value)")
}.map { value in
"The result is \(value)"
}

// `newFuture` is now a Future that will only succeed when the original Future succeeds with a value > 0
```

#### Recover

It's now also possible to provide a "catch-all" handler to recover a failing `Future`:

```swift
let numberOfItemsTask: Future = doLongRunningTask()
.flatMap { result in
result.processAndPersist()
}.map { result in
result.numberOfItems
}.recover {
cache.lastNumberOfItems
}

numberOfItemsTask.onSuccess { numberOfItems in
// This will be called even if one of the previous operations fails, with the rescue value `cache.lastNumberOfItems`
// ...
}
```

#### Zip

```swift
// Example for zip

let first: Future = doFoo()
let second: Future = doBar()

let zipped = first.zip(second).onSuccess { (anInteger, aString) in
// you get an Int and a String here
}

// or:

let first: Future = doFoo()
let second: Result = doBar()

let zipped = first.zip(second).onSuccess { (anInteger, aString) in
// you get an Int and a String here
}
```

#### Reduce

```swift
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future] = doFoo()

// With this `reduce` call we calculate the total number of items
let sumOfServerResults = serverRequests.reduce(0, combine: +).onSuccess {
// We get here only if all futures succeed
print("Sum of results is \($0)")
}
```

#### MergeAll

```swift
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future] = doFoo()

// With this `mergeAll` call we collapse the requests into one containing the result of all of them, if they all succeeded, or none if one fails
let allServerResults = serverRequests.mergeAll().onSuccess { results in
// We get here only if all futures succeed
// `results` is an [Int]
}
```

#### All

`all` behaves exactly like `mergeAll`, except that it doesn't bring the success values with it.

```swift
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future] = doFoo()

// With this `all` call we collapse the requests into one that will succeed if all of the elements succeed, otherwise it will fail
let allServerResults = serverRequests.mergeAll().onSuccess {
// We get here only if all futures succeed
}
```

#### MergeSome

```swift
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future] = doFoo()

// With this `mergeSome` call we collapse the requests into one containing the result of just the ones that succeed
let allServerResults = serverRequests.mergeSome().onSuccess { results in
// We get here and results.count == the number of succeeded requests
// `results` is an [Int]
}

// Note: `merge` succeeds only when _all_ requests succeed, while `mergeSome` always succeeds and filters out the failed requests from the results
```

#### FirstCompleted

```swift
// Let's assume this value contains a list of server requests where each request comes from a different server but all of them answer the same question
let serverRequests: [Future<[Product]>] = gatherRequests()

/// With this `firstCompleted` call we basically declare we are interested in only the first result and want to discard the remaining ones
let firstResult = serverRequests.firstCompleted().onSuccess { products in
// We get here with the first completing request
}
```

#### Traverse

```swift
// Let's assume this list contains some product identifiers
let productIdentifiers: [Int] = basketProductsIds()

// With this `traverse` call we create a Future for every identifier (for instance to retrieve details of each product), and we merge the results into one final Future
let allProductDetails = productIdentifiers.traverse({ productId in
// Let's assume this call returns a Future
ProductManager.retrieveDetailsForProduct(productId)
}).onSuccess { products in
// We get here only if all futures succeed
// `products` is a [Product]
}
```

#### Snooze

```swift
// Sometimes we may be running multiple operations in parallel and we may want to have some time in between to gather multiple values
let firstOperation = doFoo()
let secondOperation = doBar()

// With this call to `snooze` we declare we're not interested in immediate feedback from the second operation because we may want to process the result of the first, first.
secondOperation.snooze(0.5).onSuccess { value in
}
```

#### Timeout

```swift
// Sometimes we may want to set an upper bound to the time an operation can run before moving on or showing something to the user
let longRunningOperation = doFoo()

longRunningOperation.timeout(after: 5).onFailure { err in
if let error = err as? FutureError where error = FutureError.Timeout {
// The operation timed out, but of course it's still running. We may keep adding observers to the original variable if we are still interested in the final result (see next lines)
showAlert()
}
}

longRunningOperation.onSuccess { value in
showSuccessDialog()
}

```

#### Retry

```swift
// Sometimes we want to retry a given block of code for a certain number of times before failing
retry(3, every: 0.5) {
return networkManager.fetchLatestMessages() // This returns a Future
}.onSuccess { messages in
// The operation succeeded at least once
}.onFailure { _ in
// The operation failed 4 times (1 + retry count of 3)
}
```

### Function composition

`Pied Piper` can also be helpful when you want to compose the result of asynchronous computation in a single function or object.

There are 3 public functions as of `Pied Piper` 0.7:

- Compose functions

```swift
func randomInt() -> Int {
return 4 //Guaranteed random
}

func stringifyInt(number: Int) -> String {
return "\(number)"
}

func helloString(input: String) -> String {
return "Hello \(input)!"
}

let composition = randomInt >>> stringifyInt >>> helloString

composition() //Prints "Hello 4!"
```

If one of the functions returns an `Optional`, and at call time the value is `nil`, the computation stops there:

```swift

func randomInt() -> Int {
return 4 //Guaranteed random
}

func stringifyInt(number: Int) -> String? {
return nil
}

func helloString(input: String) -> String {
return "Hello \(input)"
}

let composition = randomInt >>> stringifyInt >>> helloString

composition() //Doesn't print
```

- Compose `Future`s:

```swift

func intFuture(input: Int) -> Future {
return Future(input)
}

func stringFuture(input: Int) -> Future {
return Future("Hello \(input)!")
}

let composition = intFuture >>> stringFuture

composition(1).onSuccess { result in
print(result) //Prints "Hello 1!"
}
```

## Tests

`Pied Piper` is thouroughly tested so that the features it's designed to provide are safe for refactoring and as bug-free as possible.

We use [Quick](https://github.com/Quick/Quick) and [Nimble](https://github.com/Quick/Nimble) instead of `XCTest` in order to have a good BDD test layout.

As of today, there are around **600 tests** for `Pied Piper` (see the folder `Tests`).

## Future development

`Pied Piper` is under development and [here](https://github.com/spring-media/PiedPiper/issues) you can see all the open issues. They are assigned to milestones so that you can have an idea of when a given feature will be shipped.

If you want to contribute to this repo, please:

- Create an issue explaining your problem and your solution
- Clone the repo on your local machine
- Create a branch with the issue number and a short abstract of the feature name
- Implement your solution
- Write tests (untested features won't be merged)
- When all the tests are written and green, create a pull request, with a short description of the approach taken

## Apps using Pied Piper

- [Die Welt Edition](https://itunes.apple.com/de/app/welt-edition-digitale-zeitung/id372746348?mt=8)

Using Pied Piper? Please let us know through a Pull request, we'll be happy to mention your app!

## Authors

`Pied Piper` was made in-house by WeltN24

### Contributors:

Vittorio Monaco, [[email protected]](mailto:[email protected]), [@vittoriom](https://github.com/vittoriom) on Github, [@Vittorio_Monaco](https://twitter.com/Vittorio_Monaco) on Twitter

## License

`Pied Piper` is available under the MIT license. See the LICENSE file for more info.

## Acknowledgements

`Pied Piper` internally uses:

- Some parts of `ReadWriteLock.swift` (in particular the pthread-based read-write lock) belonging to **Deferred** (available on [Github](https://github.com/bignerdranch/Deferred))