PromiKit原理概览

1,704 阅读3分钟

之前我们说了PromiseKit今天就带领大家来分析一下这个PromiseKit,之后我们再根据PromiseKit的思想剥茧抽丝的简易的撸一个微型的PromiseKit。

Promise的继承关系如下图所示


从上图建议的大纲预览我们可以看到Promise继承于Thenable这个protocol而整体的核心思想在Thenable中,Promise只是对这个思想进行了扩展,当然了假如说swift中protocol能够进行实例化的话Promise完全就没必要存在啦…

看完了基本的主线关系图我们来说说PromiseKit的核心思想--装箱和开箱。Promise认为所有的数据都需要先装箱然后开箱,采用层级传递的方式,将数据进行一次装箱封装传递出去而在装箱过程中数据是可以被加工的的。而对于下一级的流程会告诉你上一级我需要什么样的数据而上一级会需要根据下一级以及上一级的数据进行转化以满足下一级的需求。因此可以认为任何一级都需要对上指定接收格式,对下进行格式转换。

说到了装箱我们来看看出现的关键环节Box

/// - Remark: not protocol ∵ http://www.russbishop.net/swift-associated-types-contclass Box<T> {    func inspect() -> Sealant<T> { fatalError() }    func inspect(_: (Sealant<T>) -> Void) { fatalError() }    func seal(_: T) {}}final class SealedBox<T>: Box<T> {    let value: T
    init(value: T) {        self.value = value    }    override func inspect() -> Sealant<T> {        return .resolved(value)    }}

这个Box是一个简易的抽象类,定义了数据的装箱和取数据行为,而更加具体的功能则在子类中,如上SealedBox只是简单的进行了数据的存储功能,更加具体则在下面的子类中:

class EmptyBox<T>: Box<T> {    private var sealant = Sealant<T>.pending(.init())    private let barrier = DispatchQueue(label: "org.promisekit.barrier", attributes: .concurrent)
    override func seal(_ value: T) {        var handlers: Handlers<T>!        barrier.sync(flags: .barrier) {            guard case .pending(let _handlers) = self.sealant else {                return  // already fulfilled!            }            handlers = _handlers            self.sealant = .resolved(value)        }
        //FIXME we are resolved so should `pipe(to:)` be called at this instant, “thens are called in order” would be invalid        //NOTE we don’t do this in the above `sync` because that could potentially deadlock        //THOUGH since `then` etc. typically invoke after a run-loop cycle, this issue is somewhat less severe
        if let handlers = handlers {            handlers.bodies.forEach{ $0(value) }        }
        //TODO solution is an unfortunate third state “sealed” where then's get added        // to a separate handler pool for that state        // any other solution has potential races    }
    override func inspect() -> Sealant<T> {        var rv: Sealant<T>!        barrier.sync {            rv = self.sealant        }        return rv    }
    override func inspect(_ body: (Sealant<T>) -> Void) {        var sealed = false        barrier.sync(flags: .barrier) {            switch sealant {            case .pending:                // body will append to handlers, so we must stay barrier’d                body(sealant)            case .resolved:                sealed = true            }        }        if sealed {            // we do this outside the barrier to prevent potential deadlocks            // it's safe because we never transition away from this state            body(sealant)        }    }}

EmptyBox可谓是整个装箱开箱的主体,实现装箱开箱完的全部功能。我们就一点点的屡一下思路……

先从成员变量说起:

sealant:默认数据是混沌的需要等待基于一个数据

barrier:是GCD中的知识,意为栅栏,需要等待当前以及以上代码执行完才可执行下面流程(保证数据的多线程下安全)

再来看看行为:

override func seal(_ value: T) 的目的很简单就是将数据封装给已有的handler具体的怎么使用后续我们会举例

override func inspect() -> Sealant<T>和
override func inspect(_ body: (Sealant<T>) -> Void)

只是简单取出Box中数据展示给别人看…
箱子看完了,我们看看怎么实现装箱和拆箱吧:Promise

public enum Result<T> {    case fulfilled(T)    case rejected(Error)}

public final class Promise<T>: Thenable, CatchMixin {    let box: Box<Result<T>>
    fileprivate init(box: SealedBox<Result<T>>) {        self.box = box    }
    /**      Initialize a new fulfilled promise.
      We do not provide `init(value:)` because Swift is “greedy”      and would pick that initializer in cases where it should pick      one of the other more specific options leading to Promises with      `T` that is eg: `Error` or worse `(T->Void,Error->Void)` for      uses of our PMK < 4 pending initializer due to Swift trailing      closure syntax (nothing good comes without pain!).
      Though often easy to detect, sometimes these issues would be      hidden by other type inference leading to some nasty bugs in      production.
      In PMK5 we tried to work around this by making the pending      initializer take the form `Promise(.pending)` but this led to      bad migration errors for PMK4 users. Hence instead we quickly      released PMK6 and now only provide this initializer for making      sealed & fulfilled promises.
      Usage is still (usually) good:
          guard foo else {              return .value(bar)          }     */    public class func value(_ value: T) -> Promise<T> {        return Promise(box: SealedBox(value: .fulfilled(value)))    }
    /// Initialize a new rejected promise.    public init(error: Error) {        box = SealedBox(value: .rejected(error))    }
    /// Initialize a new promise bound to the provided `Thenable`.    public init<U: Thenable>(_ bridge: U) where U.T == T {        box = EmptyBox()        bridge.pipe(to: box.seal)    }
    /// Initialize a new promise that can be resolved with the provided `Resolver`.    public init(resolver body: (Resolver<T>) throws -> Void) {        box = EmptyBox()        let resolver = Resolver(box)        do {            try body(resolver)        } catch {            resolver.reject(error)        }    }
    /// - Returns: a tuple of a new pending promise and its `Resolver`.    public class func pending() -> (promise: Promise<T>, resolver: Resolver<T>) {        return { ($0, Resolver($0.box)) }(Promise<T>(.pending))    }
    /// - See: `Thenable.pipe`    public func pipe(to: @escaping(Result<T>) -> Void) {        switch box.inspect() {        case .pending:            box.inspect {                switch $0 {                case .pending(let handlers):                    handlers.append(to)                case .resolved(let value):                    to(value)                }            }        case .resolved(let value):            to(value)        }    }
    /// - See: `Thenable.result`    public var result: Result<T>? {        switch box.inspect() {        case .pending:            return nil        case .resolved(let result):            return result        }    }
    init(_: PMKUnambiguousInitializer) {        box = EmptyBox()    }}

从代码我们看到Promise是一个final类型的class不可进行继承,而内部

let box: Box<Result<T>>Box存储的是一个enum的数据(包含正常和error。整体的数定义十分的简单就是一些初始化。而关键的位置在于

public func pipe(to: @escaping(Result<T>) -> Void) 公有两个作用 1 将正常的数据通过闭包传递出去共外部使用 2 自身混沌的数据再次装箱给handler以便后续对数据处理

下面我们来看看Thenable这个根源怎么玩的


    func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> {        let rp = Promise<U.T>(.pending)        pipe {            switch $0 {            case .fulfilled(let value):                on.async(flags: flags) {                    do {                        let rv = try body(value)                        guard rv !== rp else { throw PMKError.returnedSelf }                        rv.pipe(to: rp.box.seal)                    } catch {                        rp.box.seal(.rejected(error))                    }                }            case .rejected(let error):                rp.box.seal(.rejected(error))            }        }        return rp    }

    then的功能很简单就是起到一个缓和缓冲的目的,就好比说话时候的简单停顿一样,其目的并不是做数据和逻辑转化只是简单将数据原本不变的传给下一级

    例如:

    firstly {               URLSession.shared.dataTask(.promise, with: url1)           }.then { response in               transform(data: response.data)           }.done { transformation in               //…           }

    其余的功能大家根据Thenable的源码自己分析,大体功能就是数据转换,降纬等译增加便捷性……将层的数据回调变为一级级的数据传递……

    后续我们会慢慢分析其余的简便功能:race after when等便捷功能