Skip to content

zhxnlai/Async

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Async

Async, await control flow for Swift.

async/await turns this:

// example credit to: http://promisekit.org/chaining
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    let md5 = md5ForData(data)
    dispatch_async(dispatch_get_main_queue()) {
        self.label.text = md5
        UIView.animateWithDuration(0.3, animations: {
            self.label.alpha = 1
            }) {
            // this is the end point
            // add code to happen next here
        }
    }
}

into:

async {
    let md5 = md5ForData(data)
    await { async(.Main) { self.label.text = md5 } }
    await { UIView.animateWithDurationAsync(0.3) {self.label.alpha = 1} }
    // this is the end point
    // add code to happen next here
}() {}

Installation

Async is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "SwiftAsync"

Usage

The example project and test file will help you get started.

To run the example project, clone the repo, and run pod install from the Example directory first.

Prerequisites:

async

Here is how you create an async function:

let createImage = async {() -> UIImage in
    sleep(3)
    return UIImage()
}

Here is how you call an async function, by supplying a callback:

createImage() {image in
  // do something with the image
}

Here is how you create an async function with parameters:

let fetchImage = {(URL: NSURL) in
    async {() -> UIImage in
        // fetch the image synchronously
        let image = get(URL)
        return image
    }
}

fetchImage(URL)() {image in
    // do something with the image
}

Let's define more async functions:

let processImage = {(image: UIImage) in
    async {() -> UIImage in
        sleep(1)
        return image
    }
}

let updateImageView = {(image: UIImage) in
    async(.Main) {
        self.imageView.image = image
    }
}

Instead of chaining async functions with callbacks, use await:

print("creating image")
createImage {image in
    print("processing image")
    processImage(image)() {image in
        print("updating imageView")
        updateImageView(image)() {
            print("updated imageView")
        }
    }
}

async {
    print("creating image")
    var image = await { createImage }
    print("processing image")
    image = await { processImage(image) }
    print("updating imageView")
    await { updateImageView(image) }
    print("updated imageView")
}() {}

await

await is a blocking/synchronous function. Therefore, it should never be called in main thread. It executes an async functions, which is a closure of type (T -> Void) -> Void, and returns the result synchronously.

async {
    // blocks the thread until callback is called
    let message = await {(callback: (String -> Void)) in
        sleep(1)
        callback("Hello")
    }
    print(message) // "Hello"
}() {}

// equivalent to
async {
    let message = await {
        async {() -> String in sleep(1); return "Hello" }
    }
    print(message) // "Hello"
}() {}

// equivalent to
async {
    sleep(1)
    let message = "Hello"
    print(message) // "Hello"
} {}

Here is how to use await to wrap asynchronous APIs (eg. network request, animation, ...) and make them synchronous.

let session = NSURLSession(configuration: .ephemeralSessionConfiguration())

let get = {(URL: NSURL) in
    async { () -> (NSData?, NSURLResponse?, NSError?) in
        await {callback in session.dataTaskWithURL(URL, completionHandler: callback).resume()}
    }
}

// with unwrapping
let get2 = {(URL: NSURL) in
    async { () -> (NSData, NSURLResponse)? in
        let (data, response, error) = await {callback in session.dataTaskWithURL(URL, completionHandler: callback).resume()}
        guard let d = data, r = response where error != nil else { return nil }
        return (d, r)
    }
}

async {
  if let (data, response) = await {get2(NSURL())} {
    // do something
  }
}() {}

thunkify

Alternatively, you can use thunkify, which turns functions with trailing closure into an async functions

extension UIView {
    class func animateWithDurationAsync(duration: NSTimeInterval, animations: () -> Void) -> (Bool -> Void) -> Void {
        return thunkify(.Main, function: UIView.animateWithDuration)(duration, animations)
    }
}

async {
  await { UIView.animateWithDurationAsync(0.3) {self.label.alpha = 1} }
}() {}

Serial vs Parallel

To run async functions in series, we can use for/while loops since await is blocking/synchronous.

let URLs = [NSURL]()
async {
    var results = [NSData]()
    for URL in URLs {
        results.append(await { get(URL) })
    }
    print("fetched \(results.count) items in series")
}() {}

To run async functions in parallel, call await with an array or a dictionary of async functions.

let URLs = [NSURL]()
async {
    let results = await(parallel: URLs.map(get))
    print("fetched \(results.count) items in parallel")
}() {}

Additional APIs

By default, async functions are scheduled in the global concurrent queue that has quality of service QOS_CLASS_USER_INITIATED. To schedule on a different queue:

let taskOnMainThread = async(.Main) {
    // do something
}

let customQueue = dispatch_queue_create("CustomQueueLabel", DISPATCH_QUEUE_CONCURRENT)
let taskOnCustomQueue = async(.Custom(customQueue)) {
    // do something
}

By default, await waits forever for the async function to finish. To add a timeout:

async {
    await(timeout: 0.4) { async { () -> Bool in NSThread.sleepForTimeInterval(0.3); return true } }
}() {value in}

Error handling

async$ and await$ share the same API with async and await. In addition, they handle thrown errors:

enum Error: ErrorType {
    case TestError
}

let willThrow = async$ {() throws in
    NSThread.sleepForTimeInterval(0.05)
    throw Error.TestError
}

async$ {
    try await${ willThrow }
}({(_, error) in
    expect(error).to(beTruthy())
})

Strong reference cycle

According to Strong Reference Cycles for Closures

A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.

It is helpful to add a capture list to the top level closure. Please take a look at the demo project for more examples

async {[weak self] in
    self?.doSomething()
}

License

Async is available under the MIT license. See the LICENSE file for more info.