TheChain

Async Swift tasks chaining


Keywords
async, cocoapods, futures, promises, swift, xcode
License
MIT
Install
pod try TheChain

Documentation

CI Status Version License Platform

TheChain

TheChain helps you write more clearer and readable async code:

override func viewDidLoad() {
    super.viewDidLoad()
    
    downloadImageData().onStart {
        print("Downloading started")
    }.onCompletion {
        print("Downloading finished")
    }.then(
        cacheDownloadedData
    ).then(
        readImageFromCache
    ).catch { (error) in
        print("❌ Failed to load image: \(error.localizedDescription)")
    }.finally(on: .main) { (image) in
        self.imageView.image = image
    }
}
    
// MARK: - Async
    
private func downloadImageData() -> Link<Data> {
    return URLSession.shared.dataTaskLink(with: "https://lorempixel.com/1000/1000/")
}

private func cacheDownloadedData(_ data: Data) -> Link<Void> {
    return Link(on: .global(), {
        let url = try URL.imageDataFileUrl.orThrow(Error.fileUrlIsNil)
            
        try data.write(to: url)
    })
}
    
private func readImageFromCache() -> Link<UIImage> {
    return Link(onGlobalQueue: {
        let url = try URL.imageDataFileUrl.orThrow(Error.fileUrlIsNil)
            
        let data = try Data(contentsOf: url)
            
        return try UIImage(data: data).orThrow(Error.unableToInitImage)
    })
}

Basic usage

All you need is create new Link object with desired result type, run you async code and pass result or error when async work done.

func asyncMethod() -> Link<String> {
    let link = Link<String>()
    
    DispatchQueue.global().async {
        //Some async work goes here
        link.finish("Async work result")
        
        //or
        //link.throw(Error.someError)
    }
    
    return link
}

func someMethod() {
    asyncMethod().catch { (error) in
        // Handle error
    }.finally { (result) in
        // Handle the String result
    }
}

Chaining

You can create a chain using Links just like this:

func asyncFoo() -> Link<String> {
    ...
}

func asyncBar(_ input: String) -> Link<Double> {
    ...
}

func asyncBaz(_ input: Double) -> Link<Bool> {
    ...
}

asyncFoo().then(asyncBar).then(asyncBaz).catch { (error) in
    // Handle error
}.finally { (result) in
    // Handle the Bool result
}

Extensions

TheChain comes with some handy extensions which helps you write even more compact, boilerplate-free code.

DispatchQueue

func readImageFromCache() -> Link<UIImage> {
    return Link(onGlobalQueue: {
        let url = try URL.imageDataFileUrl.orThrow(Error.fileUrlIsNil)
            
        let data = try Data(contentsOf: url)
            
        return try UIImage(data: data).orThrow(Error.unableToInitImage)
    })
}

Instead of:

func readImageFromCache() -> Link<UIImage> {
    let link = Link<UIImage>()
    
    DispatchQueue.global().async {
        guard let url = URL.imageDataFileUrl else {
            return link.throw(Error.fileUrlIsNil)
        }
        
        do {
            let data = try Data(contentsOf: url)
            
            guard let image = UIImage(data: data) else {
                return link.throw(Error.unableToInitImage)
            }
            
            link.finish(image)
        } catch {
            link.throw(error)
        }
    }
    
    return link
}

URLSession

func loadPosts() -> Link<[Post]> {
    return URLSession.shared.dataTaskLink(with: "https://jsonplaceholder.typicode.com/posts")
}

Instead of:

func loadPosts() -> Link<[Post]> {
    let link = Link<[Post]>()
    
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
        return link.throw(Error.unableToInitUrl)
    }
    
    URLSession.shared.dataTask(with: URLRequest(url: url)) { (data, _, error) in
        if let data = data {
            do {
                let posts = try JSONDecoder().decode([Post].self, from: data)
                
                link.finish(posts)
            } catch {
                link.throw(error)
            }
        } else if let error = error {
            link.throw(error)
        }
    }.resume()
    
    return link
}

Installation

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

pod 'TheChain'

License

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