RxSwift 5 更新了什么

362 阅读5分钟

原文:Medium: What’s new in RxSwift 5 @Shai Mishali

RxSwift 5 终于在几天前发布了,我认为这是一个很好的机会来分享这个版本中最值得注意的变化。

不过不用担心,因为这个版本大部分是向前兼容的,只有一些弃用和重命名。但它还包含一系列底层改进,我将在下面详细介绍。

Relays 现在是一个单独的框架 ——RxRelay

Relay 是基于 Subject 之上的一个很棒的抽象层,它可以让你发出元素,而不必关心 error 或 completed 事件。自从它被添加到 RxSwift 中以来,就作为 RxCocoa 项目中的一部分存在。

一些开发人员对此并不满意,因为他们如果要使用 Relays 就必须引入 RxCocoa,即便他们编写的代码与 RxCocoa 没有任何关系。这其实是很不合理的。并且这样一来 Linux 用户就无法使用 Relays,因为 Linux 无法导入 RxCocoa。

由于上述原因,我们将 Relays 移入了自己的框架 - RxRelay - 并调整了 RxSwift 的依赖关系图,如下所示:

RxSwift 4RxSwift 5

这允许你仅使用 RxSwift 和 RxRelay,如果不需要,则无需依赖 RxCocoa,并且还与 RxJava 保持一致,因为它是一个单独的框架。

注意:这是向后兼容的更改,因为 RxCocoa 直接导入 RxRelay。这意味着,你可以继续导入 RxCocoa,而无需导入 RxRelay,一切都会像以前一样工作。

TimeInterval → DispatchTimeInterval

RxSwift 5 中对 Schedulers 进行了重构,以弃用 TimeInterval 的使用,转而使用 DispatchTimeInterval。当需要亚秒级别的计时精度时,这可以实现更好的事件调度粒度度和更高的稳定性。

他会影响到所有基于时间的操作符,如:debouncetimeoutdelaytake 等等。作为额外的收获,他还解决了 take 入参的歧义,因为之前无法判断参数代表多少秒,还是多少个。

RxSwift 4.x

RxSwift 4 使用 TimeInterval

observable.delay(3, scheduler: MainScheduler.instance)
observable.throttle(0.5, scheduler: MainScheduler.instance)
observable.window(timeSpan: 2.5, count: 3, scheduler: MainScheduler.instance)
observable.take(1, scheduler: MainScheduler.instance)

RxSwift 5.x

RxSwift 5 使用 DispatchTimeInterval

observable.delay(.seconds(3), scheduler: MainScheduler.instance)
observable.throttle(.milliseconds(500), scheduler: MainScheduler.instance)
observable.window(timeSpan: .milliseconds(2500), count: 3, scheduler: MainScheduler.instance)
observable.take(.seconds(1), scheduler: MainScheduler.instance)

Variable 最终被弃用

Variable 是 RxSwift 早期添加的概念,它基本上让你通过 “setting” 和 “getting” 当前值来桥接命令式编程。这似乎是让开发人员开始使用 RxSwift 直到他们完全理解 “反应式思维” 的一个有用的措施。

这种构造被证明是有问题的,因为它被开发人员严重滥用来创建高度命令式的系统,而不是使用 Rx 的声明式性质。这对于反应式编程的初学者来说尤其常见,并且从概念上讲,许多人无法理解这是一种不好的做法和编码陋习。这就是为什么 Variable 已在 RxSwift 4.x 中被软弃用并带有运行时警告。

在 RxSwift 5 中,它现在已被正式完全弃用,如果你需要这种行为,推荐的方法是使用 BehaviorRelay(或 BehaviorSubject)。

RxSwift 4.x

RxSwift 4.x 对 Variable 进行了软弃用:

let variable = Variable("Hello, RxSwift")
variable.value = "Goodbye, RxSwift"
print(variable.value) // "Goodbye, RxSwift"

RxSwift 5.x

RxSwift 5.x 完全弃用了 Variable

let relay = BehaviorRelay(value: "Hello, RxSwift")
relay.accept("Goodbye, RxSwift")
print(relay.value) // "Goodbye, RxSwift"

额外的 do(on:) 重载

当你想要执行一些副作用(例如日志记录)或只是 “监听” 流的中间过程时, do 是一个很好的操作符。

为了与 RxJava 保持一致,RxSwift 现在不仅提供 do(onNext:),还提供重载后的功能,例如 do(afterNext:)onNext 表示元素被发射的时刻,而 afterNext 表示元素被发射并推向下游之后的时刻。

RxSwift 4.x

RxSwift 4.x 提供了 do(onNext:onError:onCompleted:)

Observable.of("🍎", "🍐", "🍊", "🍋")
    .do(onNext: { print(Intercepted:", $0") },
        onError: { print(Intercepted error:", $0") },
        onCompleted: { print(Completed:", $0") })

RxSwift 5.x

RxSwift 5.x 中还有 do(afterNext:afterError:afterCompleted:)

Observable.of("🍎", "🍐", "🍊", "🍋")
    .do(onNext: { print(Intercepted:", $0") },
        afterNext: { print(Intercepted after:", $0") },
        onError: { print(Intercepted error:", $0") },
        afterError: { print(Intercepted after error:", $0") },
        onCompleted: { print(Completed:", $0") },
        afterCompleted: { print(After completed:", $0") })

bind(to:) 现在支持多个观察者

在某些情况下,你必须将可观察序列绑定到多个观察者。在 RxSwift 4 中,你通常只需复制绑定代码:

RxSwift 4 一次只允许绑定到一个观察者:

isFromEnabled
    .bind(to: txtName.rx.isEnabled)
    .disposed(by: disposeBag)

isFromEnabled
    .bind(to: txtEmail.rx.isEnabled)
    .disposed(by: disposeBag)
    
isFromEnabled
    .bind(to: txtPassword.rx.isEnabled)
    .disposed(by: disposeBag)
    
isFromEnabled
    .bind(to: btnSubmit.rx.isEnabled)
    .disposed(by: disposeBag)

RxSwift 5 现在支持一次绑定到多个观察者:

isFromEnabled
    .bind(to: txtName.rx.isEnabled,
              txtEmail.rx.isEnabled,
              txtPassword.rx.isEnabled,
              btnSubmit.rx.isEnabled)
    .disposed(by: disposeBag)

这仍然解析为单个一次性,这意味着它与单观察者变体向后兼容。

一个新的 CompactMap 运算符

作为开发者,你经常要处理包含可选类型的可观察序列。为了解包这些值,社区有自己的解决方案,例如 RxSwiftExt 中的 unwrap 运算符或 RxOptional 中的 filterNil

RxSwift 5 添加了一个新的 compactMap 运算符以与 Swift 标准库保持一致,将此功能引入核心库。

RxSwift 4.x:

RxSwift 4 中没有内置解包包含可选类型的可观察序列:

observable // Observable<String?>
    .filter { $0 != nil }
    .map { $0! } // Observable<String>
    
observable.unwrap() // RxSwiftExt

observable.filterNil() // RxOptional

RxSwift 5.x

RxSwift 5.x 提供 compactMap 来解包包含可选类型的可观察序列

observable.compactMap { $0 }

toArray() 现在返回 Single<T>

toArray() 是一个操作符,一旦可观察序列完成,它将整个序列作为数组发出。

自从 RxSwift 诞生以来,这个运算符总是返回一个 Observable<T>,但是由于 Traits 的引入 —— 具体来说,Single,将返回类型更改为 Single<T> 是有意义的,以提供该类型安全性并保证仅从该运算符获取单个发出的值。

RxSwift 4.x:

toArray() 在 RxSwift 4.x 中返回 Observable<T>

observable.toArray() // Observable<T>

RxSwift 5.x:

toArray() 在 RxSwift 5.x 中返回 Single<T>

observable.toArray() // Single<T>

泛型约束命名更新

RxSwift 是泛型约束的重度使用者。从早期开始,该库就使用单字母约束来描述某些类型。例如,ObservableType.E 表示 Observable 可观察序列的泛型类型。

这工作正常,但会导致一些约束混淆,例如 O 代表不同场景中的 ObservableObserver,或者 S 代表 SubjectSequence

此外,这些单字母约束没有提供良好的自记录代码,并且使非贡献者很难理解参考文献。

出于这些原因,我们彻底修改了私有和公共接口的大多数泛型约束,以提供更多信息和详细信息。

影响最广泛的重命名是将 EElementType 更名为 Element

RxSwift 4.x

在 RxSwift 4 中扩展 Observable 使用 E 泛型约束:

extension Observable where E == String {
    func myFancyOperator() -> Observable<String> {
        return map { fancify($0) }
    }
}

RxSwift 5.x

在 RxSwift 5 中扩展 Observable 使用 Element 泛型约束:

extension Observable where Element == String {
    func myFancyOperator() -> Observable<String> {
        return map { fancify($0) }
    }
}

泛型重命名相当广泛。这是其中大部分完整的列表。这些更改大部分与 RxSwift 的内部 API 相关,其中只有少数更改会影响作为开发人员的您:

  • EElementType 已重命名为 Element
  • TraitType 已重命名为 Trait
  • SharedSequence.S 更名为 SharedSequence.SharingStrategy
  • O 被适当地重命名为 ObserverSource
  • CS 分别重命名为 CollectionSequence
  • S 在适当的情况下也被重命名为 Subject
  • R 更名为 Result
  • ReactiveCompatible.CompatibleType 已重命名为 ReactiveCompatible.ReactiveBase

社区项目

许多 RxSwift 社区项目已经迁移到 RxSwift 5 并发布了相应的版本,因此迁移过程应该相对顺利。已迁移的一些项目包括:RxSwiftExt、RxDataSources、RxAlamofire、RxOptional 等。

总结

上面列出的更改是面向开发人员的大部分更改,但还有许多较小的修复超出了此类帖子的范围,例如完全修复与 Linux 下的 Swift 5 的兼容性、小异常等。

请随意查看完整的变更日志并参与官方存储库中的讨论:github.com/ReactiveX/R…

希望你喜欢这篇文章:-)

有任何疑问吗?欢迎在下面评论。