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.