Latest 1.0.0
Homepage https://github.com/mitchtreece/Flow
License MIT
Platforms ios 8.0
Dependencies Threader
Authors

Flow

Fluid chain-able task execution. Based on Bolts-Swift.

Version
License
Platform

Overview

Flow makes dependent tasks simple to write, execute, and react upon. Using a flow task-chain, you can compose complex dependent tasks in a
straight-forward & readable fashion.

Installation

CocoaPods

Flow is integrated with CocoaPods!

  1. Add the following to your Podfile:
    use_frameworks!
    pod 'Flow-Swift'
  2. In your project directory, run pod install
  3. Import the Flow module wherever you need it
  4. Profit

Manually

You can also manually add the source files to your project.

  1. Clone this git repo
  2. Add all the Swift files in the Flow/ subdirectory to your project
  3. Flow depends on Threader. Make sure to add it to your project as well.
  4. Profit

Flow

A Flow encapsulates the various parts of a greater task into dependent bite-sized chunks. A simple Flow might look something like this:

fetchFullSizedImage().nextOnSuccess { (image) -> Flow<UIImage> in
    return scaleImage(image, 0.5)
}.nextOnSuccess { (image) -> Flow<UIImage> in
    return convertImageToBlackAndWhite(image)
}.finallyOnSuccess { (image) in
    /* Do something with your scaled, black-and-white image */
}

The above fetches a full-size image, scales that image down 50%, converts the scaled image to black-and-white, then gives it back to do what you want with. This is all assuming the fetchFullSizedImage(), scaleImage(image: UIImage, _ scale: CGFloat), and convertImageToBlackAndWhite(image: UIImage) functions are already implemented.

The above chain only moves to the next task if the previous one is successful. Hence the nextOnSuccess. If any of the tasks fail, the remaining tasks in the flow will not be called. There are times you want to continue through an optional task if it fails, to achieve this we can use the next flow control.

fetchFullSizedImage().next { (flow: Flow<UIImage>) -> Flow<UIImage> in

    if let image = flow.result {
        return scaleImage(image, 0.5)
    }

    let placeholderImage = UIImage(named: "placeholder")
    return scaleImage(placeholderImage, 0.5)

} ...

Unlike nextOnSuccess, next provides a Flow<UIImage> to the task closure. This let’s you do actions depending on the result or state of the previous flow. In addition to just checking if the previous flow has a valid result value, you can also check if it completed with an error or was cancelled:

fetchFullSizedImage().next { (flow: Flow<UIImage>) -> Flow<UIImage> in

    if flow.error != nil {
        print("Error: (flow.error!)")
    }
    else if flow.cancelled {
        print("flow was cancelled")
    }
    else if flow.result != nil {
        /* Flow completed successfully, do something with the result */
    }

} ...

Both next and nextOnSuccess expect another Flow to be returned from the task closure. This is the Flow that gets carried to the next closure. If you do not need to execute another task, you can use the finally or finallyOnSuccess flow controls to end your task chain:

fetchFullSizedImage().nextOnSuccess { (image) -> Flow<UIImage> in
    return scaleImage(image, 0.5)
}.nextOnSuccess { (image) -> Flow<UIImage> in
    return convertImageToBlackAndWhite(image)
}.finallyOnSuccess { (image) in
    /* Your task chain ends here */
}

Composing Fluid Functions

You’ve seen how easy creating flow task-chains is. Now let’s look at creating fluid functions to use with them. Take the fetchFullSizedImage() function from above, the implementation of that function might look something like this:

func fetchFullSizedImage() -> Flow<UIImage> {

    let context = FlowContext<UIImage>()

    let request = NSURLRequest(URL: NSURL(string: "http://yoursite.com/image/full")!)
    NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in

        if let data = data where error == nil {

            if let image = UIImage(data: data) { context.setResult(image) }
            else {
                let error = NSError(domain: "com.yourapp",
                                    code: -1,
                                    userInfo: [NSLocalizedDescriptionKey: "Couldn't load image from data"])
                context.setError(error)
            }

        }
        else {
            let error = NSError(domain: "com.yourapp",
                                code: -1,
                                userInfo: [NSLocalizedDescriptionKey: "Couldn't fetch image data"])
            context.setError(error)
        }

    }.resume()

    return context.flow

}

A FlowContext is created for your function. You are responsible for updating the context’s state throughout your task’s execution. You can do this using the setResult(), setError(), and cancel() functions on FlowContext. Then all you have to do is return your context’s Flow.

Thread Specification

The next, nextOnSuccess, finally, and finallyOnSuccess functions all have an optional thread parameter that can be passed to explicitly specify how a task closure gets executed.

fetchFullSizedImage().next(Thread.Main) { (image) -> Flow<UIImage> in
    /* Do stuff here */
}

The above would run it’s closure on the main-thread. There are many other options that Thread provides. For more information, take a look at Threader. By not specifying a thread, flow runs task closures in Default execution mode – which should be sufficient in most situations.

Flow Helpers

Flow also has extra helper functions to make life easier:

class func whenAllFinish(flows: [Flow]) -> Flow<[ResultType?]> {}
class func whenAllSucceed(flows: [Flow]) -> Flow<[ResultType]> {}
class func whenAnyFinish(flows: [Flow]) -> Flow<ResultType> {}
class func withDelay(delay: NSTimeInterval) -> Flow<Void> {}

These class functions return a Flow once their condition has been met, for example:

let fetchImageOne: Flow<UIImage> = fetchImage(1)
let fetchImageTwo: Flow<UIImage> = fetchImage(2)
let fetchImageThree: Flow<UIImage> = fetchImage(3)

let flow = Flow.whenAllSucceed([fetchImageOne, fetchImageTwo, fetchImageThree])
flow.finallyOnSuccess { (images) in
    /* Do something with image array */
}

The above returns an array of all the results once every flow passed in has successfully finished.

Contributing

Pull-requests are more than welcome. The goal of Flow was to create a lightweight way to run dependent tasks. I only ask that any additions made to this library keep things simple & straight-forward.

Latest podspec

{
    "name": "Flow-Swift",
    "module_name": "Flow",
    "version": "1.0.0",
    "summary": "Fluid chain-able task execution.",
    "description": "Flow makes dependent tasks simple to write, execute, and react upon. Using a flow task-chain, you can compose complex dependent tasks in anstraight-forward & readable fashion.",
    "homepage": "https://github.com/mitchtreece/Flow",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "Mitch Treece": "[email protected]"
    },
    "source": {
        "git": "https://github.com/mitchtreece/Flow.git",
        "tag": "1.0.0"
    },
    "social_media_url": "https://twitter.com/MitchTreece",
    "platforms": {
        "ios": "8.0"
    },
    "source_files": "Flow/Classes/**/*",
    "dependencies": {
        "Threader": [
            "1.0.0"
        ]
    }
}

Pin It on Pinterest

Share This