Contents

dannys42/ClosureChain

Simplifies sequential async completion methods for Swift. It makes no promises and has no futures. ;-)

Installation

Swift Package Manager

Add the ClosureChain package to the dependencies within your application's Package.swift file. Substitute "x.y.z" with the latest ClosureChain release.

.package(url: "https://github.com/dannys42/ClosureChain.git", from: "x.y.z")

Add ClosureChain to your target's dependencies:

.target(name: "example", dependencies: ["ClosureChain"]),

Cocoapods

Add ClosureChain to your Podfile:

pod `ClosureChain`

Usage

Typically in Swift, network or other async methods make use of completion handlers to streamline work.  A typical method signature looks like this:

```swift
    func someAsyncMethod(_ completion: (Data?, Error?)->Void) {
    }
```

However, this can become difficult to manage when you need to perform a number of async functions, each relying on success data from the previous call.

Normally this requires nesting the async methods or the use of a state machine.  Both of which can be difficult to reason about.

Closure Chains simplify this by allowing the developer to treat each async call as linkage of throwable closures. (i.e. links in a chain), with a single `catch` closure to manage any errors.

### Simple Example

```swift
    let chain = ClosureChain()
    chain.try { link in
        someAsyncMethod() { data, error in 
            if let error = error {
                link.throw(error)                   // use `link.throw()` since completion block is not throwable
            }
            guard let data = data else {
                link.throw(Failure.missingDdata)    // use `link.throw()` since completion block is not throwable
                return
            }
            // do something with `data`

            link.success()                          // required
        }
    }
    chain.catch { error in
        // error handler
    }
    chain.start()                                   // required to start executing links
```

Note the familiar `try-catch` pattern.  However `try` is perfomed on the chain `chain`, and the `throw` is performed on the `link`.  As a convenience, you can simply use the Swift `throw` command directly within a try-block.

There are two additional required functions:

 - `link.success()` is required to let `ClosureChain` know when an async task is complete
 - `chain.start()` is required to kick off execution of the chain.  No links will be executed until the `.start()` command is initiated.

### Passing data

The above is not very useful when we only have one async operation.  But what if we have several async operations that we wish to perform.  For example imagine we are attempt to perform this sequence of tasks:

 * Get raw image data from network
 * Convert raw data to a UIImage object.  Perhaps we have a long running async task here that will perform decryption, digital signature verification, and JSON deserialization
 * Do more background processing on UIImage
 * Notify the user we're done

For simplicity, we'll assume all our async methods are using the `Result` protocol.

This is how this might look with `ClosureChain`:

```swift
function closureChainExample() {
    let chain = ClosureChain()
    chain.try { link in
        getDataAsync() { result: Result<Data,Error> in  // Result type is provided solely for context in this example
            switch result {
            case .failure(let error):
                link.throw(error)       // use link.throw() since completion handler is not throwable
            case .success(let data):
                link.success(data)      // Pass `data` to the next link
            }
        }
    }

    chain.try { data: Data, link in     // `data` type must match prior link.success() (this check is performed at run-time)
        convertToUIImage(data) { result: Result<UIImage,Error> in   // Result type is provided solely for context in this example
            switch result {
            case .failure(let error):
                link.throw(error)       // use link.throw() since completion handler is not throwable
            case .success(let uiimage):
                link.success(uiimage)   // Pass `uiimage` to the next link
            }
        }
    }

    chain.try { image: UIImage, link in // `image` type must match prior link.success()
        processImage(image) { error: Error? in      // Error type is provided solely for context in this example
            do {
                if let error = error {
                    throw(error)        // can use do-catch to allow `throws` to pass to `link.throw()`
                }
                link.success()          // Go to next link with no passed data
            } catch {
                link.throw(error)
            }
        }
    }
    chain.try { link in                 // It is safe to ignore the passed parameter from the last `link.success()`
        // Notify the user we're done
        link.success()                  // Required even though this is the last link
    }

    chain.catch { error in
        // error handler
    }
    chain.start()                       // Required to start executing links
}
```

Notes:
 * `chain` can be safely allowed to fall out-of-scope.  `chain` and the
   associated closures will not be released from memory until `link.success()`
   in the last link is called.
 * ClosureChains make no use of DispatchQueue or OperationQueue.  Therefore
   there is no guarantee that any link is executing on any specific
   queue/thread.

### Results can be Even Better

If your async methods have completion handlers that take a single Result parameter, such as:

```swift
    func someAsyncMethod(_ completion: (Result<Data, Error>)->Void) {
    }
```

You can further streamline your code:


```swift
function closureChainExample() {
    let chain = ClosureChain()
    chain.try { link in
        getDataAsync() { result: Result<Data,Error> in  // Result type is provided solely for context in this example
            link.return(result)           // calls link.throw() or link.success() appropriately
        }
    }

    chain.try { data: Data, link in     // `data` type must match prior link.success() (this check is performed at run-time)
        convertToUIImage(data) { result: Result<UIImage,Error> in   // Result type is provided solely for context in this example
            link.return(result)         // calls link.throw() or link.success() appropriately
        }
    }

    chain.try { image: UIImage, link in // `image` type must match prior link.success()
        processImage(image) { result: Result<UIImage,Error> in      // Result type is provided solely for context in this example
            link.return(result)
        }
    }
    chain.try { link in                 // It is safe to ignore the passed parameter from the last `link.success()`
        // Notify the user we're done
        link.success()                  // Required even though this is the last link
    }

    chain.catch { error in
        // error handler
    }
    chain.start()                       // Required to start executing links
}
```

API Documentation

For more information visit our API reference.

License

This library is licensed under Apache 2.0. The full license text is available in LICENSE.

Package Metadata

Repository: dannys42/ClosureChain

Homepage: https://dannys42.github.io/ClosureChain/

Stars: 2

Forks: 0

Open issues: 0

Default branch: main

Primary language: swift

License: Apache-2.0

README: README.md

Archived: yes