一般咱们将消息通讯分红同步和异步两种,其中同步就是消息的发送方要等待消息返回才能继续处理其它事情,而异步就是消息的发送方不须要等待消息返回就能够处理其它事情。异步容许咱们同时作更多事情,同时得到更高的性能。异步也是比较灵活复杂的,不是本文重点不过多阐述。git
在 GCD (Grand Central Dispatcher) 当中,若是咱们想要在非主线程当中执行某些操做的话,那么咱们就须要闭包的帮忙。github
func asyncFunction(callback:(Result) -> Void) -> Void {
dispatch_async(queue) {
let x = Result("Hello!")
callback(x)
}
}
复制代码
封装了一个 asyncFunction
方法,从中我建立了某种 Result
,可是若是我想要将这个 Result
回调的话,那么我就必需要提供一个回调。相似代码相信你必定写过。swift
当一项任务须要分红多个异步阶段完成时,就须要在每一个阶段的回调函数中加入下阶段回调的代码,最终产生下面这样金字塔形状的代码:api
func processImageData1(completionBlock: (result: Image) -> Void) {
loadWebResource("dataprofile.txt") { dataResource in
loadWebResource("imagedata.dat") { imageResource in
decodeImage(dataResource, imageResource) { imageTmp in
dewarpAndCleanupImage(imageTmp) { imageResult in
completionBlock(imageResult)
}
}
}
}
}
processImageData1 { image in display(image)}
复制代码
这个嵌套的回调使得问题追踪变得很困难。能够想象当回调层次继续增长时,代码有多恐怖。Futures/Promises 能够帮咱们摆脱回调的困扰。promise
Future&Promise 来源于函数式语言,概念早在 1977 年就已经提出来了。其目的是分离一个值和产生值的方法,从而简化异步代码的处理。微信
Future 是一个只读的值的容器,它的值在将来某个时刻会被计算出来(产生这个值的行为是异步操做)。 Promise 是一个可写的容器,能够设置 Future
的值。网络
A Promise is something you make to someone else.(承诺是你对别人的承诺)闭包
In the Future you may choose to honor (resolve) that promise, or reject it.(在将来,您能够选择兑现(解决)该承诺,或拒绝承诺。)app
咱们能够看下面这个例子,体会下他们的威力:异步
采用常规的回调写法
class UserLoader {
typealias Handler = (Result<User>) -> Void
func loadUser(withID id: Int, completionHandler: @escaping Handler) {
let url = apiConfiguration.urlForLoadingUser(withID: id)
let task = urlSession.dataTask(with: url) { [weak self] data, _, error in
if let error = error {
completionHandler(.error(error))
} else {
do {
let user: User = try unbox(data: data ?? Data())
self?.database.save(user) {
completionHandler(.value(user))
}
} catch {
completionHandler(.error(error))
}
}
}
task.resume()
}
}
复制代码
采用 Futures & Promises
后,代码将变为
class UserLoader {
func loadUser(withID id: Int) -> Future<User> {
let url = apiConfiguration.urlForLoadingUser(withID: id)
return urlSession.request(url: url)
.unboxed()
.saved(in: database)
}
}
// 调用代码
let userLoader = UserLoader()
userLoader
.loadUser(withID: userID)
.observe { result in
// 处理 result
}
复制代码
能够很容易发现采用 Futures & Promises
后,代码量瞬间减小一大半。这里面到底有啥猫腻?
Future 正如上面所说是一个通过异步操做后返回过来的一个只读值的容器,能够在给他赋值的时候触发回调列表里回调方法的调用。
enum Result<T> {
case value(T)
case error(Error)
}
class Future<Value> {
fileprivate var result: Result<Value>? {
// Observe whenever a result is assigned, and report it
didSet { result.map(report) }
}
private lazy var callbacks = [(Result<Value>) -> Void]()
func observe(with callback: @escaping (Result<Value>) -> Void) {
callbacks.append(callback)
// If a result has already been set, call the callback directly
result.map(callback)
}
private func report(result: Result<Value>) {
for callback in callbacks {
callback(result)
}
}
}
复制代码
Promise
是 Future
的一个子类,它提供了用于兑现和拒绝的承诺方法。 兑现承诺结果将给 Future
设置一个完成的值,拒绝的承诺将给 Future
设置一个 error。
class Promise<Value>: Future<Value> {
init(value: Value? = nil) {
super.init()
// If the value was already known at the time the promise
// was constructed, we can report the value directly
result = value.map(Result.value)
}
func resolve(with value: Value) {
result = .value(value)
}
func reject(with error: Error) {
result = .error(error)
}
}
复制代码
根据上面的实现,咱们能够给 URLSession 添加一个 Extension:
extension URLSession {
func request(url: URL) -> Future<Data> {
// Start by constructing a Promise, that will later be
// returned as a Future
let promise = Promise<Data>()
// Perform a data task, just like normal
let task = dataTask(with: url) { data, _, error in
// Reject or resolve the promise, depending on the result
if let error = error {
promise.reject(with: error)
} else {
promise.resolve(with: data ?? Data())
}
}
task.resume()
return promise
}
}
复制代码
有了这些后,咱们就能够调用一个简单的网络请求
URLSession.shared.request(url: url).observe { result in
// Handle result
}
复制代码
return urlSession.request(url: url)
.unboxed()
.saved(in: database)
复制代码
网络请求封装完成,可是 unboxed
和 saved
又是如何实现?
在实现这些方法以前,咱们须要实现一个基础性的方法,chained
:
extension Future {
func chained<NextValue>(with closure: @escaping (Value) throws -> Future<NextValue>) -> Future<NextValue> {
// Start by constructing a "wrapper" promise that will be
// returned from this method
let promise = Promise<NextValue>()
// Observe the current future
observe { result in
switch result {
case .value(let value):
do {
// Attempt to construct a new future given
// the value from the first one
let future = try closure(value)
// Observe the "nested" future, and once it
// completes, resolve/reject the "wrapper" future
future.observe { result in
switch result {
case .value(let value):
promise.resolve(with: value)
case .error(let error):
promise.reject(with: error)
}
}
} catch {
promise.reject(with: error)
}
case .error(let error):
promise.reject(with: error)
}
}
return promise
}
}
复制代码
基于 chained
咱们能够容易实现 saved
:
extension Future where Value: Savable {
func saved(in database: Database) -> Future<Value> {
return chained { user in
let promise = Promise<Value>()
database.save(user) {
promise.resolve(with: user)
}
return promise
}
}
}
复制代码
虽然 chaining
提供了顺序执行异步操做的方式,但有些时候咱们只想进行简单的同步转换获取里面的值。为此咱们在给 Future 添加个 transformed
方法:
extension Future {
func transformed<NextValue>(with closure: @escaping (Value) throws -> NextValue) -> Future<NextValue> {
return chained { value in
return try Promise(value: closure(value))
}
}
}
复制代码
转换是连接操做的同步版本,在构造新的 Promise 时候,对值进行转换转换。这种操做有个很是适合 JSON 解析或将一种类型的值转换为另外一种类型。
咱们能够将一个值为 Data 类型的Future 转换为值为 Unboxable 类型的 Future:
extension Future where Value == Data {
func unboxed<NextValue: Unboxable>() -> Future<NextValue> {
return transformed { try unbox(data: $0) }
}
}
复制代码
经过上面 Future & Promises 的实现,UserLoader 能够很愉悦的实现
class UserLoader {
func loadUser(withID id: Int) -> Future<User> {
let url = apiConfiguration.urlForLoadingUser(withID: id)
// Request the URL, returning data
let requestFuture = urlSession.request(url: url)
// Transform the loaded data into a user
let unboxedFuture: Future<User> = requestFuture.unboxed()
// Save the user in the database
let savedFuture = unboxedFuture.saved(in: database)
// Return the last future, as it marks the end of the chain
return savedFuture
}
}
复制代码
上面的方式调用有没有发现感受是在调用同步代码同样。
经过链式调用,代码将会是咱们一开始提到的那种方式处理:
class UserLoader {
func loadUser(withID id: Int) -> Future<User> {
let url = apiConfiguration.urlForLoadingUser(withID: id)
return urlSession.request(url: url)
.unboxed()
.saved(in: database)
}
}
复制代码
上面是比较简化的 Future&Promise
。在 github 中有不少开源库能够参考:
为何本文会提到 Future/Promise
这个概念,重点仍是想说下 SwiftNIO。在 SwiftNIO 中也融入了这个概念。在 Vapor3 的时候咱们会大量看到 Future
这样的返回,而后也会有不少链式方法的调用。
若是一开始没有接触这个概念,理解上可能会有必定的难度。
那在 SwiftNIO 是如何实现这个异步机制的?
下期咱们将进行讲解!
咱们从一个最初的 UserLoader
的闭包回调到 Future&Promise
的链式调用,让咱们能够知道能够这种模式去处理异步。在实现上也不难理解,核心点 Future 表示一个将来的值,Promise 用来设置 Future 的值,Chaining(对现有的 Future 进行操做,获取到当前 Future 的值经过 Promise 进行处理,返回一个新的 Future。) 是个利器,可细细体会一番。
更多阅读,可订阅官方微信公众号: