一般咱們將消息通訊分紅同步和異步兩種,其中同步就是消息的發送方要等待消息返回才能繼續處理其它事情,而異步就是消息的發送方不須要等待消息返回就能夠處理其它事情。異步容許咱們同時作更多事情,同時得到更高的性能。異步也是比較靈活複雜的,不是本文重點不過多闡述。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。) 是個利器,可細細體會一番。
更多閱讀,可訂閱官方微信公衆號: