PromiseKit源码解析

154 阅读9分钟

介绍

一般业务开发时我们对于异步任务通过原生的closure方式作为回调,如果遇上多个有依赖异步任务就会造成多层嵌套,写出来的代码阅读性也不好,如下例子:

loadFromDataBase() { result in
    switch result {
    case .success:
        networkRequest() { result in
            switch result {
            case .success:
                queue.async {
                    // Do Somethings ...
                    DispatchQueue.main.async {
                        // Update UI
                    }
                }
            case .failure(let error):
                // Handle Error  
            }
        }
    case .failure(let error):
        // Handle Error    
    {
}

为了解决这类问题,项目中引入PromiseKit能优雅的处理异步回调,提高代码的阅读性:

firstly {
    loadFromDataBase()
}.then {
    networkRequest()
}.map(on: queue) {
    // Do Somethings ...
}.done {
    // Update UI
}.catch {
    // Handle Error
}

那问题来了,为什么这段代码能写得这么有意思?closure没有变量引用为什么也能正确执行?closure的生命周期是怎样的?


源码分析

首先我们看一下Promise的定义能更好的帮助我们分析源码:

public final class Promise<T>: Thenable, CatchMixin {
    let box: Box<Result<T>>
}

过滤一些暂时不需要的内容来看,Promise遵守了Thenable和CatchMixin协议,Promise内部有一个Box属性,而Box关联了Result类型,这些类型我们接下来慢慢分析(其实这里我们可以联想一下,这里有个箱子,就是让我们来装东西的,可能装就是一个值,当然箱子也有空箱和装了东西的状态)。

Result

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

对于一个结果,成功时保存一个值,失败时保存一个错误。这个是保存promise被赋值的结果,一般我们由下面这种使用方式:

let (promise, resolver) = Promise<Int>.pending()
resolver.fuifull(123)
// or
resolver.reject(error)

下面是Resolver中的fulfill和reject方法的定义,我们暂时不需要关心box和seal是什么,我们只需要知道通过resolver来赋值最终会使用Result来包装这个结果。

/// Fulfills the promise with the provided value
func fulfill(_ value: T) {
   box.seal(.fulfilled(value))
}
/// Rejects the promise with the provided error
func reject(_ error: Error) {
   box.seal(.rejected(error))
}

Handlers

final class Handlers<R> {
    var bodies: [(R) -> Void] = []
    func append(_ item: @escaping (R) -> Void) { 
        bodies.append(item) 
    }
}

Handlers是将值类型的数组包装成一个引用类型的对象,可以看成就是引用类型的数组(NSArray)。为什么需要包装成引用类型呢?后面分析Promise这个类时会有解释。

Sealant

enum Sealant<R> {
    case pending(Handlers<R>)
    case resolved(R)
}

pending代表promise还没被赋值处于等待的状态,所以pending会携带一个Handlers(引用类型的数组)来保存当promise被赋值后时需要执行的任务,例如在介绍部分使用的then、map、done、catch这几个方法的闭包都会存储在Handlers的数组中。而resolved则代表promise被赋值后的状态,所以resolved会保存被赋的值。在Promise中真正用来存储值和需要执行的闭包的就是Sealant这个类型。

Box

class Box<T> {
    func inspect() -> Sealant<T> { fatalError() }
    func inspect(_: (Sealant<T>) -> Void) { fatalError() }
    func seal(_: T) {}
}

Box是整个PromiseKit的核心基类,Box这看起来像是个协议,但是为什么不声明成协议而是一个基类呢?代码注释中有篇文章解释到,原因简单来说是因为Box使用了泛型,如果使用的协议带有关联类型或者Self,那这个协议是不能直接当做类型来使用的。如下使用Protocol来声明会出现编译错误。(Swift5已经支持了)

protocol Box {
    associatedtype T
    func test(_ value: T)
}

struct MyClass<T> {
    let box: Box<T>
    
    init(box: Box<T>) {
        self.box = Box
    }
}
// Compile Failure: Protocol 'Box' can only be used as a generic constraint because it has Self or associated type requirements

Box定义了两个inspect方法都是获取Sealant的状态,而seal(_:)则是给Sealant赋值的方法,我们再看一遍Resolver的fulfill和reject方法的定义,正是通过seal(_:)来传入一个Result来赋值的。

/// Fulfills the promise with the provided value
func fulfill(_ value: T) {
   box.seal(.fulfilled(value))
}
/// Rejects the promise with the provided error
func reject(_ error: Error) {
   box.seal(.rejected(error))
}

SealedBox

Box的一个派生类是SealedBox,在创建实例的同时赋值:

final class SealedBox<T>: Box<T> {
    let value: T

    init(value: T) {
        self.value = value
    }

    override func inspect() -> Sealant<T> {
        return .resolved(value)
    }
}

SealedBox顾名思义就是初始化时直接赋值,所以通过inspect方法获取Sealant时就是带有值的resolved状态。虽然使用promise时一般使用pending方法来构造,等待异步回调后通过Resolver来赋值,但是也有构造promise时直接赋值的使用场景:

let p1 = Promise.value(true)
let p2 = Promise(error: error)

我们看源码分析这部分最上面Promise的定义可以发现,因为Promise内部有一个box属性,对于这种构造promise时直接赋值的使用场景,我们不难会想到这个box的类型是SealedBox,然后我们来看看这个value方法的定义:

public final class Promise<T>: Thenable, CatchMixin {
    let box: Box<Result<T>>
    
    public init(error: Error) {
        box = SealedBox(value: .rejected(error))
    }
    
    public static func value(_ value: T) -> Promise<T> {
       return Promise(box: SealedBox(value: .fulfilled(value)))
    }
}

EmptyBox

Box的第二个派生类是EmptyBox,最常用的我们使用时都是等待异步回调结果,再把结果塞到box里:

class EmptyBox<T>: Box<T> {
    // 创建pengind状态的sealant,并包含一个Handlers
    private var sealant = Sealant<T>.pending(.init())
    // 用于sealant读写线程安全
    private let barrier = DispatchQueue(label: "org.promisekit.barrier", attributes: .concurrent)

    /**
    通过这个方法改变sealant状态,并执行pending时添加的任务
    */
    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 = _handlers
            // 改变sealant状态
            self.sealant = .resolved(value)
        }
        if let handlers = handlers {
            // 执行处于pending时添加的任务
            handlers.bodies.forEach{ $0(value) }
        }
    }

    /**
    通过这个方法获取sealant状态
    */
    override func inspect() -> Sealant<T> {
        var rv: Sealant<T>!
        barrier.sync {
            rv = self.sealant
        }
        return rv
    }

    /**
    通过这个方法获取sealant状态,并在线程安全下的执行操作
    */
    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)
        }
    }
}

先来看给box设置值的seal(_:)方法,就是在线程安全下改变sealant的状态并执行存储的任务。

然后我们来看中间的inspect方法,在线程安全下读取sealant并返回,可以查看sealant的状态。虽然sealant是个值类型,但是如果sealant处于pending状态时,携带的Handlers是个引用类型,值类型拷贝后Handlers还是同一个引用对象,如果通过这个返回的sealant来改变Handlers的话是会存在线程安全的问题,因此这个方法只适用于查询。

既然中间的inspect方法只适用于查询,那需要更改sealant的话就需要用到最后面的inspect(_:)方法了。作者的注释也写的挺详细的,当sealant处于pending时,我们需要往Handlers添加任务,所以这个body闭包就需要在线程安全下执行,因为可能同时在几个线程中添加任务,又或者在某个线程中执行了seal(_:)方法需要更改sealant的为resolved状态。如果sealant已经处于resolved状态了,则在外面执行这个闭包以避免死锁的情况,因为处于resolved状态后,sealant就不会再被更改了。

Resolver

public final class Resolver<T> {
    let box: Box<Result<T>>

    init(_ box: Box<Result<T>>) {
        self.box = box
    }

    func fulfill(_ value: T) {
        box.seal(.fulfilled(value))
    }

    func reject(_ error: Error) {
        box.seal(.rejected(error))
    }

    func resolve(_ result: Result<T>) {
        box.seal(result)
    }
    
    // 其他的resolve方法...
}

Resolver其实就是对Box.seal(_:)这个方法的封装,因为Box的访问权限是internal的,所以我们只能通过Resolver来赋值,并且变得更简单。

Promise

enum PMKUnambiguousInitializer {
    case pending
}

public final class Promise<T>: Thenable, CatchMixin {
    let box: Box<Result<T>>
    
    init(_: PMKUnambiguousInitializer) {
        box = EmptyBox()
    }

    public class func pending() -> (promise: Promise<T>, resolver: Resolver<T>) {
        return { ($0, Resolver($0.box)) }(Promise<T>(.pending))
        // 展开后就是干了这些事情,Promise和Resolver的Box其实是同一个对象
        // let promise = Promise<T>(.pending) // 其实就是创建一个EmptyBox
        // let resolver = Resolver(promise.box)
        // return (promise, resolver)
    }

    /// - See: `Thenable.pipe`
    public func pipe(to: @escaping(Result<T>) -> Void) {
        // 这里的box是EmptyBox
        // 查询box中sealant的状态,此时更改pending的Handlers是不合适的,因为线程不安全
        switch box.inspect() {
        case .pending:
            // 处于pending状态,需要添加任务,因此执行线程安全的 inspect(_:) 方法
            box.inspect {
                // 此时在线程安全的情况下执行
                switch $0 {
                case .pending(let handlers):
                    // 往Handlers中添加任务
                    handlers.append(to)
                case .resolved(let value):
                    // 已经赋值了,直接执行任务
                    to(value)
                }
            }
        case .resolved(let value):
            // 已经赋值了,直接执行任务
            to(value)
        }
    }
}

我们最常用的是pending方法返回了一个Promise和Resolver,并且它们的Box是同一个对象,所以我们就能理解为什么通过Resolver来赋值后能够触发Promise的任务了,因为真正存储和执行任务的是Box这个对象,而Promise和Resolver就是对Box的封装。

既然Resolver是给Box赋值的,那么给Box添加任务的就是Promise了。Promise有一个pipe(to:)的方法,它是Thenable这个协议里定义要实现的方法,这个命名很形象,我们调用then、map、done、catch等方法时会生成一个任务链,看介绍部分的代码,用起来就像是个管道,传入的闭包就是一个任务,每个任务执行的结果会进入下面的管道。通过这个方法,如果Box中的Sealant处于pending状态,则在线程安全的情况下添加任务到Sealant的Handlers中,如果已经处于resolved状态,则立刻执行任务。

Thenable

/// Thenable represents an asynchronous operation that can be chained.
public protocol Thenable: class {
    associatedtype T
    /// `pipe` is immediately executed when this `Thenable` is resolved
    func pipe(to: @escaping(Result<T>) -> Void)
    /// The resolved result or nil if pending.
    var result: Result<T>? { get }
}

pipe(to:)的方法,我们从刚才的Promise的实现中知道具体干了的事情。至于我们平时使用的then、map、done等这些方法是怎么实现的?答案是通过面向协议对Thenable进行扩展来实现的!因为Thenable里已经声明了负责添加任务的pipe(to:)方法,所以通过这个方法就能扩展许多有用的方法,而这些方法都是处理成功(fulfilled)的的任务。因为扩展的方法太多,下面就举例分析几个常用的方法,其他的方法实现都是大同小异。

public struct PMKConfiguration {
    public var Q: (map: DispatchQueue?, return: DispatchQueue?) = (map: DispatchQueue.main, return: DispatchQueue.main)
}
public var conf = PMKConfiguration()

public extension Thenable {
    func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise<U> {
        let rp = Promise<U>(.pending) // init方法里创建一个EmptyBox,这是触发 下一个 管道任务的Promise
        // 通过 pipe(_:) 方法给 当前 的Promise添加任务
        pipe {
            // 任务执行了代表Promise被赋值(其实是Box中的Sealant被赋值,从pending变成resolved)
            switch $0 {
            case .fulfilled(let value):
                on.async(flags: flags) {
                    do {
                        // 获取当前的值执行transform,将结果赋值给触发 下一个 管道任务的Promise
                        rp.box.seal(.fulfilled(try transform(value)))
                    } catch {
                        rp.box.seal(.rejected(error))
                    }
                }
            case .rejected(let error):
                rp.box.seal(.rejected(error))
            }
        }
        return rp
        
        // 如果换成我们日常熟悉的使用方式的角度来改写这个方法的代码或许更容易理解一些
//        let (promise, resolver) = Promise<U>.pending()
//      // 这里的任务就是给resolver赋值,来触发rp的任务管道
//        pipe { result in
//            switch result {
//            case .fulfilled(let value):
//                on.async(flags: flags) {
//                    do {
//                        let newValue = try transform(value)
//                        resolver.fulfill(newValue)
//                    } catch {
//                        resolver.reject(error)
//                    }
//                }
//            case .rejected(let error):
//                resolver.reject(error)
//            }
//        }
//        return promise
    }
}

这里举例map(on:flags:_:)方法,第一个参数on传入的是一个队列,代表执行任务时所在的队列,默认值conf.Q.map是一个主队列,conf是一个全局变量,我们一般不会修改这个默认队列。而这个方法的具体实现是,在内部定义了触发下个任务管道的promise(就是临时变量rp),调用这个map(on:flags:_:)方法后,任务链的下一个任务就是通过给这个promise(rp)赋值来触发的。

public extension Thenable {
    func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
       let rp = Promise<Void>(.pending)
       pipe {
           switch $0 {
           case .fulfilled(let value):
               on.async(flags: flags) {
                   do {
                       try body(value)
                       rp.box.seal(.fulfilled(()))
                   } catch {
                       rp.box.seal(.rejected(error))
                   }
               }
           case .rejected(let error):
               rp.box.seal(.rejected(error))
           }
       }
       return rp
       
       // 如果换成我们日常熟悉的使用方式的角度来改写这个方法的代码或许更容易理解一些
//        let (promise, resolver) = Promise<U>.pending()
//      // 这里的任务就是给resolver赋值,来触发rp的任务管道
//        pipe { result in
//            switch result {
//            case .fulfilled(let value):
//                on.async(flags: flags) {
//                    do {
//                        try body(value)
//                        resolver.fulfill(())
//                    } catch {
//                        resolver.reject(error)
//                    }
//                }
//            case .rejected(let error):
//                resolver.reject(error)
//            }
//        }
//        return promise
    }
}
public extension 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
        
        // 如果换成我们日常熟悉的使用方式的角度来改写这个方法的代码或许更容易理解一些
//        let (promise, resolver) = Promise<U>.pending()
//      // 这里的任务就是给resolver赋值,来触发rp的任务管道
//        pipe { result in
//            switch result {
//            case .fulfilled(let value):
//                on.async(flags: flags) {
//                    do {
//                        let otherPromise = try body(value)
//                        guard promise !== otherPromise else { throw PMKError.returnedSelf }
//                        otherPromise.pipe { result in
//                            promise.box.seal(result)
//                        }
//                    } catch {
//                        resolver.reject(error)
//                    }
//                }
//            case .rejected(let error):
//                resolver.reject(error)
//            }
//        }
//        return promise
    }
}

Guarantee

public final class Guarantee<T>: Thenable {
    let box: PromiseKit.Box<T>
    
    init(_: PMKUnambiguousInitializer) {
        box = EmptyBox()
    }

    /// Returns a tuple of a pending `Guarantee` and a function that resolves it.
    public class func pending() -> (guarantee: Guarantee<T>, resolve: (T) -> Void) {
        return { ($0, $0.box.seal) }(Guarantee<T>(.pending))
//        let guarantee = Guarantee<T>(.pending)
//        let resolve = guarantee.box.seal // 函数 == 闭包
//        return (guarantee, resolve)
    }

    func pipe(to: @escaping(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)
        }
    }
}

guarantee跟promise很类似,唯一的区别点就是没有错误状态,一定是成功的,所以box的泛型类型是T而不是Result<T>。

由于不需要处理错误情况,对应的函数实现都是promise处理fulfilled分支的逻辑

func map<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> U) -> Guarantee<U> {
    let rg = Guarantee<U>(.pending)
    pipe { value in
        on.async(flags: flags) {
            rg.box.seal(body(value))
        }
    }
    return rg
    
    // 如果换成我们日常熟悉的使用方式的角度来改写这个方法的代码或许更容易理解一些
//    let (guarantee, resolver) = Guarantee<U>(.pending)
//    pipe { value in
//        on.async(flags: flags) {
//            let newValue = body(value)
//            resolver(newValue)
//        }
//    }
//    return guarantee
}

CatchPolicy & CancellableError

public enum CatchPolicy {
    case allErrors // 捕获所有错误
    case allErrorsExceptCancellation // 除了取消错误,其它错误都捕获
}

public protocol CancellableError: Error {
    var isCancelled: Bool { get }
}

extension Error {
    public var isCancelled: Bool {
        do {
            throw self
        } catch PMKError.cancelled {
            return true
        } catch let error as CancellableError {
            return error.isCancelled
        } catch URLError.cancelled {
            return true
        } catch CocoaError.userCancelled {
            return true
        } catch {
        #if os(macOS) || os(iOS) || os(tvOS)
            let pair = { ($0.domain, $0.code) }(error as NSError)
            return ("SKErrorDomain", 2) == pair
        #else
            return false
        #endif
        }
    }
}

CatchPolicy决定了捕获错误的逻辑,是否需要关心取消的错误,因为一般取消都是属于用户操作,而非真正的错误。同时给Swfit.Error扩展isCancelled属性作为是否为取消错误做判断。

CatchMixin & PMKFinalizer

public protocol CatchMixin: Thenable
{}

public extension CatchMixin {
    @discardableResult
    func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer {
        let finalizer = PMKFinalizer()
        pipe {
            switch $0 {
            case .rejected(let error):
                guard policy == .allErrors || !error.isCancelled else {
                    fallthrough // 跑fulfilled case
                }
                on.async(flags: flags) {
                    body(error)
                    finalizer.pending.resolve(())
                }
            case .fulfilled:
                finalizer.pending.resolve(())
            }
        }
        return finalizer
    }
}

public class PMKFinalizer {
    let pending = Guarantee<Void>.pending()
    
    public func finally(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping () -> Void) {
        pending.guarantee.done(on: on, flags: flags) {
            body()
        }
    }
}

CatchMixin类型是用来处理错误情况的,因此Promise会遵循CatchMixin。同样通过pipe去添加任务到Handlers中,并且只处理错误分支,policy决定了是否处理取消错误,最后返回一个PMKFinalizer对象,同时不管成功还是错误都会给PMKFinalizer的resolve赋值。由于函数声明加了@discardableResult关键字,所以一般在使用上会忽略掉了PMKFinalizer,而PMKFinalizer只有finally一个函数,可以添加一个不管成功或失败都会执行的任务,一般可以用于执行一些清理任务。

生命周期

func networkRequest() -> Promise<JSON> {
    let (promise1, resolver1) = Promise<JSON>.pending()
    Network.request { json in
        resolver1.fulfill(json)
    }
    return promise1
}

func mainV1() {
    firstly {
        networkRequest()
    }.map(on: queue) {
        // Do Somethings ...
    }.done {
        // Update UI
    }.catch {
        // Handle Error
    }
}

func mainV2() {
    let promise1 = networkRequest()
    // promise2被promise1的box1持有
    let promise2 = promise1.map(on: queue) {
        // Do Somethings ...
    }
    // promise3被promise2的box2持有
    let promise3 = promise2.done {
        // Update UI
    }
    // finalizer被promise3的box3持有
    let finalizer = promise3.catch {
        // Handle Error
    }
    // 函数返回只有promise1被释放
}

可以看mainV1的例子是我们常见的使用方式,如果把链式调用展开就会变成mainV2的样子,可以帮助我们更好理解生命周期,除了最开始的networkRequest返回的promise1,后续map、done、catch返回的promise和finalizer都被前一个promise的box持有,最后函数返回时,只有promise1会被释放掉,而剩余的引用关系是 Network.request -> resolver1 -> box1 -> promise2 -> box2 -> promise3 -> box3 -> finalizer

同时这个引用关系也是调用的流程