前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >根据原理撸一个带bug的PromiKit

根据原理撸一个带bug的PromiKit

作者头像
大话swift
发布2019-07-03 16:58:43
6140
发布2019-07-03 16:58:43
举报
文章被收录于专栏:大话swift大话swift

之前我们说了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-cont
class 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等便捷功能

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大话swift 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档