Iterable 和 Observable

在上一节简单的讨论了一点异步相关的问题,本节将继续讨论一些异步的事情。

上一节中同步下载图片的代码:

let imageURL = NSURL(string: "https://github.com/fluidicon.png")!
let data = NSData(contentsOfURL: imageURL)!
let image = UIImage(data: data)
self.imageView.image = image

笔者在这里尝试改成链式的写法,大体如下:

let image = NSURL(string: "https://github.com/fluidicon.png")
.map { NSData(contentsOfURL: $0)! }
.map { UIImage(data: $0)! }
imageView.image = image

更有意思一点的写法是使用 flatMap :

let image = NSURL(string: "https://github.com/fluidicon.png")
.flatMap { NSData(contentsOfURL: $0) }
.flatMap { UIImage(data: $0) }
imageView.image = image

这样的写法,通过点访问的形式对数据进行迭代,一步一步得到预期的结果。相比之前的代码,这样以迭代形式写的代码有两个优势:

更清晰的代码结构

通过一个 map 或者 flatMap ,可以清晰的表述 map 或者 flatMap 中传入的闭包只是一个步骤的处理过程,这个闭包的实现与上下文没有依赖的关系。

回到开始的处理方案,在这四行代码中,我们很难直接理解每一步的处理过程是什么。

let imageURL = NSURL(string: "https://github.com/fluidicon.png")!
// ------
let data = NSData(contentsOfURL: imageURL)!
let image = UIImage(data: data)
// ------
self.imageView.image = image

是这样吗?分三步?

还是这样的分三步:

let imageURL = NSURL(string: "https://github.com/fluidicon.png")!
let data = NSData(contentsOfURL: imageURL)!
// ------
let image = UIImage(data: data)
//------
self.imageView.image = image

或者说,他们本身就是四个小的步骤。

用了 map 或者 flatMap 就不一样的:

let image = NSURL(string: "https://github.com/fluidicon.png")
// ------
.flatMap { NSData(contentsOfURL: $0) }
// ------
.flatMap { UIImage(data: $0) }

很明显,获取图片的过程是三步。

显然,map 和 flatMap 很好的为代码进行了分层,使代码更加清晰易读。

有意思的是,为 Optional 增加一个方法,可以使代码变得更加 Iterable :

NSURL(string: "https://github.com/fluidicon.png")
.flatMap { NSData(contentsOfURL: $0) }
.flatMap { UIImage(data: $0) }
.forValue { [unowned self] in self.imageView.image = $0 }

forValue 的实现如下:

protocol Setable { }
extension Setable {
func forValue(body: Self -> Void) {
body(self)
}
}
extension Optional: Setable { }

更清晰的描述

前面的代码还可以写的更有意思些:

NSURL(string: "https://github.com/fluidicon.png")
.flatMap(download) // 下载
.flatMap(parseToImage) // 解析成 UIImage
.forValue(setImageTo(imageView)) // 设置 UIImage 到 UIImageView

相比之前的代码,这更加明确了,除了将步骤分的更清晰,还为每一个步骤都定义了个名字。具体上面三个的实现如下:

let download: NSURL -> NSData? = { url in
return NSData(contentsOfURL: url)
}
let parseToImage: NSData -> UIImage? = { data in
return UIImage(data: data)
}
func setImageTo(_ imageView: UIImageView) -> UIImage? -> Void {
return { image in
imageView.image = image
}
}

可以注意到,对于同步操作的迭代,Iterable 可以很轻松的完成需求,那么异步的迭代如何完成?比如使用 Alamofire 做网络请求:

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.responseJSON { response in
if let JSON = response.result.value {
print("JSON: \(JSON)")
// 在这里 return 吗?
}
}

这根本就无法完成。

更强大的 Iterable : Observable

首先 Iterable 可以完成的事情 Observable 一样可以完成。Observable 是 Iterable++ ,它还可以完成像上面的 Alamofire 的异步回调迭代

来看一个将 Alamofire 回调闭包转换成 Observable 的过程:

Observable<NSData>
.create { observer in
let downloadImage = request(.GET, "https://github.com/fluidicon.png")
.responseData { (response) in
if let data = response.data {
observer.onNext(data) // 这里调用 onNext 就类似于调用 return
}
observer.onCompleted()
}
return AnonymousDisposable {
downloadImage.cancel()
}
}

整段代码目前最需要关注的点是 observer.onNext(data) 类似于迭代过程中调用 return data 将值传递下去,将 return 换成 onNext 传递迭代后的值,从而可以在这种异步回调的代码中继续优雅的迭代下去。

.map { return "https://github.com/fluidicon.png" }
.map { rawURLString in return NSURL(string: rawURLString)! }
.map { url in return NSURLRequest(URL: url) }
.flatMap { urlRequest -> Observable<NSData> in
return Observable<NSData>
.create { observer in
let downloadImage = request(.GET, urlRequest)
.responseData { (response) in
if let data = response.data {
observer.onNext(data)
}
observer.onCompleted()
}
return AnonymousDisposable {
downloadImage.cancel()
}
}
}
.map { data in UIImage(data: data)! }

总结

IterableObservable 都可以更好的组织代码,让代码描述的逻辑更加清晰,此外 Observable更强大Iterable ,具有处理异步的能力。