Future & Promise 實現分析(Swift)

同步與異步

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

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

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

PromiseFuture 的一個子類,它提供了用於兌現和拒絕的承諾方法。 兌現承諾結果將給 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
}
複製代碼

Chaining

return urlSession.request(url: url)
                 .unboxed()
                 .saved(in: database)

複製代碼

網絡請求封裝完成,可是 unboxedsaved 又是如何實現?

在實現這些方法以前,咱們須要實現一個基礎性的方法,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
        }
    }
}
複製代碼

Transforms

雖然 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 中有不少開源庫能夠參考:

SwiftNIO

爲何本文會提到 Future/Promise 這個概念,重點仍是想說下 SwiftNIO。在 SwiftNIO 中也融入了這個概念。在 Vapor3 的時候咱們會大量看到 Future 這樣的返回,而後也會有不少鏈式方法的調用。

  • flatMap
  • map
  • transform
  • flatten
  • do/catch
  • catchMap/catchFlatMap
  • always
  • wait
  • request.future(_:)

若是一開始沒有接觸這個概念,理解上可能會有必定的難度。

那在 SwiftNIO 是如何實現這個異步機制的?

源碼在此

下期咱們將進行講解!

總結

咱們從一個最初的 UserLoader 的閉包回調到 Future&Promise 的鏈式調用,讓咱們能夠知道能夠這種模式去處理異步。在實現上也不難理解,核心點 Future 表示一個將來的值,Promise 用來設置 Future 的值,Chaining(對現有的 Future 進行操做,獲取到當前 Future 的值經過 Promise 進行處理,返回一個新的 Future。) 是個利器,可細細體會一番。

參考閱讀

更多閱讀,可訂閱官方微信公衆號:

相關文章
相關標籤/搜索