Tomorrowland 1.4.0

Tomorrowland 1.4.0

Lily Ballard维护。



Tomorrowland

Version Platforms Languages License CocoaPods Carthage compatible

Tomorrowland是Swift和Objective-C中对Promise的实现。Promise是对异步任务的一个包装,它提供了一种订阅任务解决的标准方式以及将promise链接起来的方式。

UIApplication.shared.isNetworkActivityIndicatorVisible = true
MyAPI.requestFeed(for: user).then { (feedItems) in
    self.refreshUI(with: feedItems)
}.catch { (error) in
    self.showError(error)
}.always { _ in
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
}

它基于PromiseKitHydra,但有几个关键区别

  • 它内部使用原子操作,而不是为每个promise创建单独的DispatchQueue。这意味着它更快,且使用资源更少。
  • 它提供了对可取消promise的全支持。PromiseKit支持检测“已取消”错误,但没有请求取消promise的方法。Hydra支持取消promise,但实际上不能停止promise执行的任务,除非promise体本身轮询取消状态(例如,一个包装网络任务的promise不能合理地取消网络任务)。Tomorrowland通过允许promise体观察取消状态,并将子promise的取消与其父promise相关联,改进了这一点。
  • 它的Obj-C支持使用泛型提高了类型安全和更好的文档。
  • 与Hydra类似,但不与PromiseKit,它提供了一种抑制已注册回调的方式(例如,因为你不再关心结果,并不要过时的数据影响你的UI)。这与promise取消不同。
  • Tomorrowland的promise对错误类型是完全泛型的,而PromiseKit和Hydra只支持使用Error作为错误类型。这可能会在构造promise时需要更多的类型,但它允许更强大的错误处理。Tomorrowland还有一些与使用Error作为错误类型的promise一起工作的便利方法。
  • Tomorrowland是完全线程安全的。我没有理由相信PromiseKit不是,但(在撰写本文时),Hydra的部分实现是错误地以非线程安全的方式进行的。

安装

手动

您可以像其他项目一样,手动将 Tomorrowland 添加到工作区,并将生成的 Tomorrowland.framework 添加到您应用程序的框架中。

Carthage

github "lilyball/Tomorrowland" ~> 1.0

项目文件配置为使用 Swift 5。代码可以编译成对 Swift 4.2,但我不熟悉任何一种方法来指示 Carthage 在编译期间覆盖 Swift 版本。

CocoaPods

pod 'Tomorrowland', '~> 1.0'

podspec 声明了同时对 Swift 4.2 和 Swift 5.0 的支持,但选择 Swift 版本需要使用 CoocaPods 1.7.0 或更高版本。在 CocoaPods 1.6 或更早版本中,Swift 版本将默认为 5.0。

SwiftPM

Tomorrowland 当前依赖于一个私有的 Objective-C 模块来实现其原子操作。这种配置意味着它不能与 Swift 包管理器 (Swift Package Manager) 兼容(因为添加兼容性将需要公开共享这个私有的 Objective-C 模块)。

用法

创建 Promise

可以使用以下类似的代码创建 Promise

let promise = Promise<String,Error>(on: .utility, { (resolver) in
    let value = try expensiveCalculation()
    resolver.fulfill(with: value)
})

这个 Promise 的主体在指定的 PromiseContext 中运行,本例中为 .utility(意味着 DispatchQueue.global(qos: .utility))。与回调不同,所有创建的 Promise 都必须指定一个上下文,以避免在主线程上意外运行开销很大的计算。可用的上下文包括 .main、每个 Dispatch QoS、特定的 DispatchQueue、特定的 OperationQueue,以及值 .immediate,表示同步运行代码块。还有一个特殊上下文 .auto,在主线程上等于 .main,否则等于 .default

注意: .immediate 上下文在使用回调处理程序时可能会很危险,大部分情况下都应该避免使用。它主要用于创建 Promise,并且每当它与回调处理程序一起使用时,处理程序必须准备好在 任何线程 上执行。对于回调处理程序,通常仅适用于短暂的线程无关回调,例如仅在取消 URLSessionTask 时执行的操作的 .onRequestCancel

Promise 的主体接收到一个“解析器”,它必须使用它来满足、拒绝或取消 Promise。如果解析器在没有使用的情况下超出了范围,Promise 将自动取消。如果 Promise 的错误类型是 Error,Promise 主体也可能抛出一个错误(如上面所示),然后该错误用于拒绝 Promise。此解析器还可以使用 resolver.onRequestCancel 来观察取消请求,如下所示。

let promise = Promise<Data,Error>(on: .immediate, { (resolver) in
    let task = urlSession.dataTask(with: url, completionHandler: { (data, response, error) in
        if let data = data {
            resolver.fulfill(with: data)
        } else if case URLError.cancelled? = error {
            resolver.cancel()
        } else {
            resolver.reject(with: error!)
        }
    })
    resolver.onRequestCancel(on: .immediate, { _ in
        task.cancel()
    })
    task.resume()
})

解析器还具有一个方便的方法 handleCallback(),该方法旨在使将框架回调包装在 Promise 中变得容易。此方法返回一个闭包,可以作为回调直接使用。它还接受一个可选的 isCancelError 参数,可以用于表示错误表示取消。例如

geocoder.reverseGeocodeLocation(location, completionHandler: resolver.handleCallback(isCancelError: { CLError.geocodeCanceled ~= $0 }))

使用 Promise

一旦拥有 Promise,就可以注册回调以在解析 Promise 时执行。大多数回调方法都需要上下文,但对于其中一些(如 thencatchalwaystryThen),可以省略上下文,它默认为 .auto,如果从主线程注册回调,则表示主线程,否则表示带有 QoS .default 的调度队列。

注册回调时,该方法还返回一个 Promise。所有回调注册方法都返回一个新的 Promise,即使回调不会影响 Promise 的值。这样做的原因是确保链式回调始终保证在新的一个开始之前,前一个回调已经完成执行,即使在使用并发上下文(例如 .utility)的情况下,并且取消返回的 Promise 不会取消原始一个,如果其上有其他回调已注册。

大多数回调注册方法都有允许您从回调中返回 Promise 的版本。在这种情况下,结果 Promise 等待您返回的 Promise 解析后再采用其值。这允许轻松组合 Promise

showLoadingIndicator()
fetchUserCredentials().flatMap(on: .default) { (credentials) in
    // This returns a new promise
    return MyAPI.login(name: credentials.name, password: credentials.password)
}.then { [weak self] (apiKey) in
    // this is invoked when the promise returned by MyAPI.login fulfills.
    MyAPI.apiKey = apiKey
    self?.transitionToLoggedInState()
}.always { [weak self] _ in
    // This is always invoked regardless of whether the previous chain was
    // fulfilled, rejected, or cancelled.
    self?.hideLoadingIndicator()
}.catch { [weak self] (error) in
    // this handles any error returned from the previous chain, meaning any error
    // from `fetchUserCredentials()` or from `MyAPI.login(name:password:)`.
    self?.displayError(error)
}

当组合返回 Promise 的回调时,您可能会遇到不兼容的错误类型问题。存在一些便利的方法来处理错误与 Error 兼容的 Promise,但它们并不覆盖所有情况。如果您发现自己遇到了这些情况之一,任何错误类型符合 ErrorPromise 都有一个 .upcast 属性,可以将该错误转换为 Error,以便更容易地组合 Promise

Tomorrowland 还提供了一个 StdPromise 的 typealias,这是 Promise 的简写。这经常非常有用,可以避免重复类型,例如使用 StdPromise(fulfilled: someValue) 代替 Promise(fulfilled: someValue)

取消和失效

所有 Promise 都暴露了一个方法 .requestCancel()。之所以这样命名,是因为这并不实际保证 Promise 会被取消。如果 Promise 支持取消,则该方法将触发一个回调,Promise 可以使用该回调来取消其工作。但不支持取消的 Promise 将忽略它,并最终按常规履行或拒绝。当然,请求取消已经解决的 Promise 一事无成,即使回调尚未调用。

为了处理 Promise 在您不再关心它之后解决的问题,存在一个称为 PromiseInvalidationToken 的独立机制,可用于抑制回调。所有回调方法都有一个可选的 token 参数,它接受一个 PromiseInvalidationToken。如果提供,则在回调执行之前在令牌上调用 invalidate(),则 guarantee the callback 将不会触发。如果回调返回的值是必需的,以解决从回调注册方法返回的 Promise,则取消该结果 Promise。可以同时使用多个回调和 PromiseInvalidationToken,并且单个令牌可以被重复使用。建议您充分利用失效令牌和取消功能。这可能会看起来像

class URLImageView: UIImageView {
    private var promise: StdPromise<Void>?
    private let invalidationToken = PromiseInvalidationToken()
    
    enum LoadError: Error {
        case dataIsNotImage
    }
    
    /// Loads an image from the URL and displays it in the image view.
    func loadImage(from url: URL) {
        promise?.cancel()
        invalidationToken.invalidate()
        // Note: dataTaskAsPromise does not actually exist
        promise = URLSession.shared.dataTaskAsPromise(with: url)
        // Use `_ =` to avoid having to handle errors with `.catch`.
        _ = promise?.tryMap(on: .utility, { (data) -> UIImage in
            if let image = UIImage(data: data) {
                return image
            } else {
                throw LoadError.dataIsNotImage
            }
        }).then(token: invalidationToken, { [weak self] (image) in
            self?.image = image
        })
    }
}

PromiseInvalidationToken 还有一个 .requestCancelOnInvalidate(_:) 方法,可以注册任意数量的 Promise,在令牌下次失效时自动请求取消(使用 .requestCancel())。Promise 也有同样的方法(除了它接受一个令牌作为参数),以便于在同一时间对令牌调用 .requestCancelOnInvalidate(_:)。这可以用于终止一个没有将 Promise 分配给本地变量的 promise chain。PromiseInvalidationToken 还有一个 .cancelWithoutInvalidating() 方法,它取消了与关联的 Promise 而不失效令牌。

默认情况下,PromiseInvalidationToken 会在 deinitialized 时自动失效。这主要用于与 requestCancelOnInvalidate(_:) 一起使用,因为它允许您在拥有令牌的对象 deinits 时自动取消您的 Promise。此行为可以通过 init 的一个可选参数来禁用。

Promise 还有一个便捷方法 requestCancelOnDeinit(_:),可以用来请求在某个对象销毁时取消 Promise。这相当于给对象增加了一个 PromiseInvalidationToken 属性(配置为销毁时失效)并在token失效时请求取消,但可以在token不会被显式销毁的情况下使用。

使用这些方法,可以将上面的 loadImage(from:) 修改如下,包括取消功能

class URLImageView: UIImageView {
    private let promiseToken = PromiseInvalidationToken()
    
    enum LoadError: Error {
        case dataIsNotImage
    }
    
    /// Loads an image from the URL and displays it in the image view.
    func loadImage(from url: URL) {
        promiseToken.invalidate()
        // Note: dataTaskAsPromise does not actually exist
        promise = URLSession.shared.dataTaskAsPromise(with: url)
        // Use `_ =` to avoid having to handle errors with `.catch`.
        _ = promise?.tryMap(on: .utility, { (data) -> UIImage in
            if let image = UIImage(data: data) {
                return image
            } else {
                throw LoadError.dataIsNotImage
            }
        }).then(token: promiseToken, { [weak self] (image) in
            self?.image = image
        }).requestCancelOnInvalidate(invalidationToken)
    }
}

失效token链式

PromiseInvalidationToken 可以组织成树形结构,使得失效一个token会级联到其他token上。这通过调用 childToken.chainInvalidation(from: parentToken) 实现。实际上,这和手动在父token失效后分别手动失效每个子token没有区别,但它提供了一个便捷的方法来实现细粒度的失效控制,同时也能简单地进行批量的失效处理。例如,你可能为不同的视图控制器创建了单独的token,所有这些token都从单个token开始链式失效,这个token在用户登出时失效,这样可以一次性自动失效所有依赖用户的网络请求,同时仍然允许每个视图控制器独立地失效自己的请求。

TokenPromise

为了避免在多个 Promise 方法中重复传递 PromiseInvalidationToken 以及取消结果promise,存在一个类型 TokenPromise 来处理这个问题。你可以通过 Promise.withToken(_:) 方法创建 TokenPromise。这允许你将如下代码

func loadModel() {
    promiseToken.invalidate()
    MyModel.fetchFromNetworkAsPromise()
        .then(token: promiseToken, { [weak self] (model) in
            self?.updateUI(with: model)
        }).catch(token: promiseToken, { [weak self] (error) in
            self?.handleError(error)
        }).requestCancelOnInvalidate(promiseToken)
}

重写为一个更简洁的形式

func loadModel() {
    promiseToken.invalidate()
    MyModel.fetchFromNetworkAsPromise()
        .withToken(promiseToken)
        .then({ [weak self] (model) in
            self?.updateUI(with: model)
        }).catch({ [weak self] (error) in
            self?.handleError(error)
        })
}

自动取消传播

几乎所有的回调注册方法都会在父对象没有其他观察者的情况下自动将取消请求从子对象传播到父对象。如果所有观察者的promise请求取消,则在此时取消请求将向上传播。这意味着只要至少有一个感兴趣的观察者,promise就不会自动取消。请注意,没有观察者的promise不会自动取消,这只会发生在至少有一个观察者的情况下(然后请求取消)。自动取消传播还要求promise本身不再在作用域内。因此,你应该避免长期保留promise,而是使用 .cancellable 属性或 PromiseInvalidationToken 的 requestCancelOnInvalidate(_:), 如果你想要能够后来取消promise。

自动取消传播也可以与实用函数 when(fulfilled:)when(first:) 以及便捷方法 timeout(on:delay:)delay(on:_:) 一起使用。

Promise 有几个方法不参与自动取消传播。您可以使用 tap(on:token:_:) 作为 always 的替代方案,以便注册一个不会干扰现有自动取消传播的观察者(这适合插入 Promise 链的中间部分)。您还可以使用 tap() 作为这个的更通用版本。

注意,ignoringCancel() 禁用了接收者的自动取消传播。一旦在 Promise 上调用此方法,它将永远不会自动取消。

传播取消 propagatingCancellation(on:cancelRequested:)

在某些情况下,您可能需要保留一个 Promise,而不阻止其子项取消传播。这里的主要用例是去重访问异步资源(如网络加载)。在这种情况下,您可能希望保留 Promise 并为每个请求相同资源的客户端返回一个新的子项,而不会阻止资源的加载取消。这可以通过保留调用 .propagatingCancellation(on:cancelRequested:) 的结果来完成。从这个方法返回的 Promise 将在所有子项请求取消时立即将其取消传播到父项,即使 Promise 仍在作用域内。当请求取消时,将在向上传播取消之前立即调用 cancelRequested 处理程序;这使您能够释放对 Promise 的引用(因此客户端的新请求将创建全新的资源加载)。使用 makeChild() 返回每个客户端的新子项。这可能的一个例子如下

func loadResource(at url: URL) {
    let promise: StdPromise<Model>
    if let existingPromise = resourceLoads[url] {
        promise = existingPromise
    } else {
        promise = makeResourceRequest(for: url).propagatingCancellation(on: .main, cancelRequested: { (promise) in
            if self.resourceLoads[url] == promise {
                self.resourceLoads[url] = nil
            }
        })
        resourceLoads[url] = promise
    }
    // Return a new child for each request so all clients have to cancel, not just one.
    return promise.makeChild()
}

特殊的 .nowOr(_:) 上下文

有一个特殊上下文 PromiseContext.nowOr(_:),其行为与其他上下文略有不同。这个上下文特别之处在于,它的回调根据其在注册时被注册的 Promise 是否已经解析而执行不同。如果 Promise 已经解析,那么 .nowOr(context) 的行为类似于 .immediate,否则它类似于封装的 context。此上下文旨在替换在注册回调之前检查 promise.result 是否非 nil 的代码。

如果在这个上下文中使用 Promise.init(on:_:),它总是表现得像 .immediate,如果在 DelayedPromise.init(on:_:) 中使用,则始终表现得像封装的上下文。

存在一个属性 PromiseContext.isExecutingNow,可以在通过 .nowOr(_:) 注册的回调中进行访问,以确定回调是同步执行还是异步执行。从任何其他上下文访问时,它返回 false。当在 PromiseContext.isExecutingNow 设置为 true 的回调中通过 .immediate 注册回调时,如果嵌套回调也是同步执行的,嵌套回调将继承 PromiseContext.isExecutingNow 标志。这是一点微妙,但目的是允许 Promise(on: .immediate, { … }) 从其周围的作用域继承标志。

这个上下文可能的使用示例之一是在从网络请求中填充图片视图时。

createNetworkRequestAsPromise()
    .then(on: .nowOr(.main), { [weak imageView] (image) in
        guard let imageView = imageView else { return }
        let duration: TimeInterval = PromiseContext.isExecutingNow
            ? 0 // no transition if we're synchronous
            : 0.25
        UIView.transition(with: imageView, duration: duration, options: .transitionCrossDissolve, animations: {
            imageView.image = image
        })
    })

Promise 辅助函数

存在几个辅助函数可用于处理多个 Promise。

when(fulfilled:)

when(fulfilled:) 是一个全局函数,它接受一个 Promise 数组或其他 2 至 6 个独立的 Promise 参数,并返回一个最终按所有输入 Promise 的值解决的单一 Promise。在数组版本中,所有输入 Promise 必须具有相同类型,并且结果是数组形式。在单个参数版本中,Promise 可能有独特的值类型(但具有相同的错误类型),结果是元组。

如果任何输入 Promise 被拒绝或取消,则结果 Promise 也会立即拒绝或取消。如果有多个输入 Promise 被拒绝或取消,第一个这样的输入将影响结果。

此函数有一个可选参数 cancelOnFailure:,如果提供为 true,将取消所有输入 Promise,如果任何其中之一被拒绝。

when(first:)

when(first:) 是一个全局函数,它接受一个相同类型的 Promise 数组,并返回一个最终采用第一个输入 Promise 同样的值或错误的单一 Promise。取消的输入 Promise 被忽略,除非所有输入 Promise 都被取消,在这种情况下,结果 Promise 也会被取消。

此函数有一个可选参数 cancelRemaining:,如果提供为 true,将在其中一个输入 Promise 被满足或拒绝时取消剩余的输入 Promise。

Promise.timeout(on:delay:)

Promise.timeout(on:delay:) 是一个方法,它返回一个新的 Promise,其值与接收者相同,或者如果在给定时间内接收者未解决,则被拒绝并显示错误。

Promise.delay(on:_:)

Promise.delay(on:_:) 是一个方法,它返回一个新的 Promise,在指定的延迟后采用与接收者相同的结果。主要用于测试目的。

Objective-C

Tomorrowland 支持与 Obj-C 的兼容性,形式为 TWLPromise<ValueType,ErrorType>。这是一个并行 Promise 实现,可以与 Promise 之间的桥接,并支持所有相同的功能。注意,一些方法名称不同(因为缺乏重载),尽管 TWLPromise 在其类型上是泛型的,但由于无法有泛型方法,返回新 Promise 的回调注册方法的返回值没有参数化。

回调生命周期

在 Promise 上注册的回调将在 Promise 解决之前保持。如果调用了一个回调(或者如果没有取消无效的无效令牌,就会调用),Tomorrowland 保证将在调用它的上下文中释放回调。如果回调未调用(例如,如果它是 then(on:_:) 回调但 Promise 被拒绝),则不对释放回调的上下文作出保证。如果您需要确保它在合适的上下文中释放(例如,如果您需要捕获必须在主线程上释放的对象),则可以使用 .always.mapResult 变体之一。

需求

要求至少支持 iOS 9、macOS 10.10、watchOS 2.0 或 tvOS 9.0。

许可

许可下述任一项:

贡献力量

除非您明确指出,否则您所提交的旨在包含在作品中的任何贡献将按上述方式双重许可,不附带任何其他条款或条件。

版本历史

v1.4.0

  • 修复了 Promise.Resolver.resolve(with:)flatMap 方法族的取消传播行为。以前,请求与解解析器(对于 resolve(with:)flatMap 方法族返回的)关联的承诺的取消,即使上游承诺有其他子项,也会立即请求取消上游承诺。新的行为修复了这一点,使它像任何其他子承诺一样参与自动取消传播(#54)。

  • 略微优化了将一个承诺连接到另一个承诺时的堆栈使用情况。

  • 避免为不涉及回调的链式Promise使用栈空间。例如,当从flatMap(on:token:_:)返回的Promise解析时,它将通过不使用额外的栈帧来解析外部Promise。你可以把它想象成尾调用函数。这不仅影响flatMap,还影响诸如tap()ignoringCancel()等操作。这也适用于Obj-C(使用TWLPromise)。

    注意:这不会影响隐式提升到Swift.Error类型的变体,例如tryFlatMap(on:token:_:)

  • 更改onCancel的取消传播行为。类似于tap,如果父项有其他子项并且所有其他子项请求取消,则它不会阻止自动取消传播。与tap不同,当没有其他子项时请求取消将在父项中传播。这里的原因是附加一个onCancel观察者不应该阻止会发生的取消,但当它是唯一的子项时,它应该像其他标准观察者一样表现(#57)。

  • 添加方法Promise.makeChild()。这返回一个新子项,该子项采用接收器的值并像任何其他观察者一样传播取消。这里的目的是在将一个父项的多个子项传回调用者时使用,因为返回父项意味着任何一个调用者都可以取消它而无需其他调用者的参与。这尤其与propagatingCancellation(on:cancelRequested:)#56)一起使用时很有用。

v1.3.0

  • 添加PromiseContext.isExecutingNow(在Obj-C中为TWLPromiseContext.isExecutingNow),如果从.nowOr(_:)注册的回调中访问,它将返回true,或者如果没有从注册到.immediate的回调(或Promise.init(on:_:))中访问,则返回false。如果从这样的回调中访问,它将继承周围作用域的PromiseContext.isExecutingNow标志。这旨在允许Promise(on: .immediate, { … })查询周围作用域的标志(#53)。
  • 为Obj-C添加方便的方法,用于执行then+catch,因为这是一种常见的模式,而链式Objective-C方法是有点尴尬的(#45)。
  • Promise.timeout的默认上下文更改为.nowOr(.auto)
  • 更改Promise.timeout(on:delay:)delay小于或等于零、上下文是.immediate.nowOr(_:)以及上游Promise尚未解析时的行为。之前超时会异步发生,并且上游Promise将有机会与新超时竞争。新行为使超时同步发生(49)。

v1.2.0

  • 添加 PromiseContext.nowOr(context) (在 Obj-C 中为 +[TWLContext nowOrContext:]),当已经解析的情况下同步运行回调,否则将回调注册在 context 上。这可以替换掉之前需要在注册回调前检查 promise.result 的代码(#34)。

    例如

    networkImagePromise.then(on: .nowOr(.main), { [weak button] (image) in
        button?.setImage(image, for: .normal)
    })
  • 添加 Promise.Resolver.hasRequestedCancel (在 Obj-C 中为 TWLResolver.cancelRequested),返回 true 如果承诺已被请求取消或已取消,否则返回 false 如果未被请求取消或已满足或拒绝。这在承诺初始化器花费了大量时间而无法通过 onRequestCancel 处理器中断的情况下非常有用(#47)。

  • Promise.timeout 的默认上下文从 .auto 改为 .nowOr(.auto)。在大多数情况下,其行为与 .auto 相同,但如果接收者已经解析,这将导致返回的承诺同样已解析(#50)。

  • 确保 when(first:cancelRemaining:) 如果所有输入承诺之前都已取消,则返回一个已取消的承诺,而不是异步取消返回的承诺(#51)。

  • 确保 when(fulfilled:qos:cancelOnFailure:) 如果所有输入承诺之前都已成功或任何输入承诺之前已被拒绝或取消,则返回一个已解决的承诺(#52)。

v1.1.1

  • 修复在清理 nil 节点之前推入新节点时在 PromiseInvalidationToken.requestCancelOnInvalidate(_:)PromiseInvalidationToken.chainInvalidation(from:includingCancelWithoutInvalidating:) 中的内存泄漏(#48)。

v1.1.0

  • 添加新的方法 .propagatingCancellation(on:cancelRequested:),可以用于创建一个持久化的Promise,在它存活期间可以将取消操作从其子节点传播到父节点。通常,Promise不会传播取消操作,直到它们自己被释放,以备将来添加更多子节点。此新方法主要用于对异步资源(如网络加载)的请求进行去重,以便在没有子节点关注该资源时可以取消资源请求(#46)。

v1.0.1

  • 抑制Swift 5.1编译器关于需要Swift 5.0编译器处理的代码的警告。

v1.0.0

  • 修复了一个相当严重的bug,其中PromiseInvalidationToken只要与任何回调相关的Promise尚未解决,就不会deinit。这意味着默认的invalidateOnDeinit行为不会触发,并且即使没有更多外部引用该令牌,回调仍然会触发,这意味着配置为在Promise无效时取消的任何Promise都不会取消。仅用于requestCancelOnInvalidate(_:)的令牌仍然会释放,并且令牌在所有关联的Promise解决后仍然会释放。
  • 调整了PromiseInvalidationToken中使用的原子内存排序。经过仔细重读,我不认为我之前发出了正确的fences,这使得可能发生与requestCancelOnInvalidate(_:)并发调用的令牌读取错误的生成值,以及在多个线程上对多个调用调用requestCancelOnInvalidate(_:)的令牌会损坏生成。
  • 添加了PromiseInvalidationToken.chainInvalidation(from:),在另一个令牌无效化时使令牌无效化。这允许同时进行细粒度和批量无效化。这种方式链在一起的令牌将永远保持链状态(#43)。
  • 将项目文件更新为Swift 5.0。源代码已支持此版本。此更改仅会影响使用Carthage或从源代码添加构建此框架的人。
  • 更新podspec以列出Swift 4.2和Swift 5.0。从CocoaPods 1.7.0或更高版本开始,您的Podfile现在可以声明与哪个版本的Swift兼容。对于使用CocoaPods 1.6或更早版本的个人,它将默认为Swift 5.0。

v0.6.0

  • 使DelayedPromise遵循Equatable (#37)。
  • 添加用于处理Swift.Result的便利函数 (#39)。
  • 将所有已弃用的函数标记为不可用。这恢复了编写如下代码的能力:promise.then({ foo?($0) }),而不会错误地解析到废弃的map(_:)形式 (#35)。
  • Promise.init(result:)Promise.init(on:result:after:)重命名为Promise.init(with:)Promise.init(on:with:after:) (#40)。

v0.5.1

  • 在同一个runloop传递中链接多个.main上下文块时,确保在执行下一个块之前释放每个块。

  • 确保如果调用了用户提供的回调,它也将在同一上下文中释放 (#38)。

    这个保证仅适用于已调用的回调(忽略令牌)。这意味着当使用如.then(on:_:)时,如果承诺得到履行,则onSuccess块将被释放在提供上下文中,但如果承诺被拒绝,则不会做出此类保证。如果您依赖释放的上下文(例如,它捕获必须在本线程上释放的对象),则可以使用.alwaysmapResult变体之一。

v0.5.0

  • PromiseTokenPromise上的许多方法重命名 (#5)。

    这消除了大多数覆盖,留下的唯一覆盖方法是那些处理Swift.ErrorE: Swift.Error的方法,甚至在Swift 5编译器中这些覆盖也已删除。

    then现在是mapflatMaprecover的覆盖现在是flatMapErroralways的覆盖现在是flatMapResult,并对try变体进行了类似的重命名。

  • 添加一个返回Void的新then方法。返回的承诺将解析为原始承诺的结果。

  • 添加新的mapErrortryMapError方法。

  • 添加新的mapResulttryMapResult方法。

  • 扩展《tryFlatMapError》以便在所有《Promise》上可用,而不仅仅是那些错误类型为《Swift.Error》的《Promise》。

  • 从大多数调用中移除默认的《.auto》值作为《on context:参数。现在它仅提供给“终端”回调,即那些从处理器不返回值的回调。这避免了在主线程上不必要地执行琐碎映射的常见问题(《a href="https://github.com/lilyball/Tomorrowland/issues/33")(#33))。

v0.4.3

  • 修复与 Xcode 10.2 / Swift 5 编译器的兼容性(《a href="https://github.com/lilyball/Tomorrowland/issues/31")(#31)、《a href="https://bugs.swift.org/browse/SR-9753")(SR-9753))。

v0.4.2

  • 添加新方法《Promise.Resolver.resolve(with: somePromise)》,使用另一个《Promise》来解决接收器(《a href="https://github.com/lilyball/Tomorrowland/issues/30")(#30))。

v0.4.1

  • 将《PromiseCancellable.requestCancel()`标记为《public》(《a href="https://github.com/lilyball/Tomorrowland/issues/29")(#29))。

v0.4

  • 当使用《PromiseContext.operationQueue》时,提高《.delay(on:_:)》和《.timeout(on:delay:)》的行为。相关的操作现在立即添加到队列中,并且只有在延迟/超时过后才准备就绪。
  • 添加《-[TWLPromise initCancelled]》以构建一个预先取消的《Promise》。
  • 添加《Promise.init(on:fulfilled:after:)》、《Promise.init(on:rejected:after:)》和《Promise.init(on:result:after:)》。这些初始化器产生的结果类似于《Promise(fulfilled: value).delay(after)`,除了它们可以立即响应当前取消。这使得它们更适合作为可取消的定时器使用,而不是《.delay(_:)`,后者更适用于调试(《a href="https://github.com/lilyball/Tomorrowland/issues/27")(#27))。
  • 在调用 PromiseInvalidationToken.requestCancelOnInvalidate(_:) 时尝试清理回调列表。被析构的承诺将从回调列表的开头被移除。这有助于防止在生命周期内使用令牌仅用于在所有者析构时取消所有承诺(而不是定期吊销)而导致回调列表无法控制地增长(《》)。
  • 如果调用 .requestCancel() 同时上游承诺被取消,则取消 .delay(_:) 计时器。这样请求的取消将会跳过早些的程序,但意外的取消仍然会延迟结果(《”)。

v0.3.4

v0.3.3

v0.3.2

  • HashableEquatable 规约添加到 PromiseInvalidationToken
  • 添加一个新的类型 TokenPromise,该类型包装一个 Promise 并自动应用 PromiseInvalidationToken。此 API 仅限 Swift 使用。

v0.3.1

  • 添加缺失的 Swift—to—ObjC 便利桥接方法。
  • Decodable 合规性添加到 NoError
  • 添加 Promise.fork(_:) 方法。
  • 解决在 Xcode 9.3 中针对 32 位 iOS 9 模拟器时的编译失败问题。
  • 修复 iOS 9 模拟器上的取消传播测试用例。

v0.3

  • Promise.requestCancelOnInvalidate(_:) 添加为 token.requestCancelOnInvalidate(_:) 的便利。
  • Promise.requestCancelOnDeinit(_:) 添加为向在 deinit 时失效的对象添加令牌属性的便利。
  • 更好地支持带 delay/timeoutOperationQueue。我们不再使用 OperationQueue 的底层队列,而是使用一个 .userInitiated 队列作为计时器,然后跳到 OperationQueue 上来解决承诺。

v0.2

  • 实现自动取消传播并删除 .linkCancel 选项。
  • 为了自动取消传播,移除了 timeout(on:delay:) 中的 cancelOnTimeout: 参数。
  • deinit 时自动使 PromiseInvalidationToken 失效。这种行为可以通过 init 中的参数来禁用。

v0.1

初版 alpha 发行。