新的 2021 年快乐!这是相当艰难的一年,我相信我们都希望有更好的一年,我们终于能够过上正常的生活。
新年伊始,还有什么比发布新版本更好的事情呢?向 RxSwift 6 问好。
这篇文章将向你快速概述一些可能影响你的最值得注意的变化。
注意:这只是一些有趣更新的部分列表,显然不包括大量较小的错误修复和改进。
有关完整的更改日志,请查看 release notes。
话不多说,让我们直接开始吧!
新 Logo
当然,这不是技术上的改变,但绝对值得一提。
RxSwift 始终使用 Reactive Extensions 的原始 Volta Eel 徽标,但我觉得这个主要版本可能是一个很好的机会,可以为 RxSwift 的徽标添加一些独特的优势。
这是一个以其自己的精神和身份使其独一无二的机会,同时仍然尊重原始的 ReactiveX 徽标以及 Swift 的徽标。
我给你,新的 RxSwift Logo!
将 Binder
从 RxCocoa 迁移到 RxSwift
这是一个微小但强烈要求的改变,而且是有意义的。 Binder
,顾名思义,允许你定义一种将 Observable
可观察序列绑定到其中的方法,以反应性地提供绑定的输入。
例如:
swift
viewModel.isButtonEnable.bind(to: myButton.rx.isEnabled)
使用底层 Binder
让你绑定到 rx.isEnabled
Binder
始终存在于 RxCocoa 内部,但我们社区的用例和各种讨论表明,它是一个超级有用的实体,可以为更广泛的 RxSwift 受众提供服务,因此它现在是其中的一部分,并且 RxCocoa 不需要使用 Binder。
使用 @dynamicMemberLookup
自动建立 Binder
s
RxSwift 包含一个名为 .rx
的命名空间,它允许你为特定对象放置自己的反应式扩展。
例如,给定一个如下所示的自定义 MyView
:
swift
class MyView: UIView {
var title: String
var subtitle: String?
var icon: UIImage?
}
创建反应式绑定的常见模式通常如下所示:
swift
extension Reactive where Base: MyView {
var title: Binder<String> {
Binder(base) { base, title in
base.title = title
}
}
var subtitle: Binder<String?> {
Binder(base) { base, subtitle in
base.subtitle = subtitle
}
}
var icon: Binder<UIImage?> {
Binder(base) { base, icon in
base.icon = icon
}
}
}
这将允许你将适当类型的可观察序列绑定到各种反应性输入:
swift
viewModel.title.bind(to: myView.rx.title)
viewModel.subtitle.bind(to: myView.rx.subtitle)
viewModel.icon.drive(myView.rx.icon)
这非常有效,甚至 RxCocoa 本身也是如此为其消费者提供响应式绑定的。
不幸的是,这种代码也相当重复和样板化。它真正所做的只是反映底层 Base 的属性。
幸运的是,从 Swift 5.1 开始,我们针对这个问题有了更好的解决方案 ------@dynamicMemberLookup
。
RxSwift 6 将自动为任何类合成所有这些 Binder
,这意味着我上面展示的所有 Binder
代码都可以完全删除,并真正清理您的代码。
只需开始在任何 AnyObject
继承类上编写 .rx
,你就会立即看到扩展基础对象的每个属性的自动合成绑定器:
不过不用担心,您自己的自定义反应式扩展仍然优先于合成的动态成员扩展,这使您可以进行更精细的控制。
RxSwift 新增 withUnretained
使用 RxSwift 和 Cocoa /iOS 代码时的常见模式是获取对 self
的弱引用,以便您可以将发出的值传递给所有者,例如:
swift
viewModel.importantInfo
.subscribe(onNext: { [weak self] info in
guard let self = self else { return }
self.doImportantTask(with: info)
})
.disposed(on: disposeBag)
注意:请小心将此运算符与缓冲运算符(例如
share(replay: 1)
)一起使用,因为它还会缓冲保留的对象,这可能会导致引用循环。如果您想要更简单的替代方案,请查看 RxSwift 6.1 中的subscribe(with:onNext:onError:onCompleted:onDispose:)
。
对于单个输出来说,这似乎很好,但想象一下它在单个代码库中出现的频率。
幸运的是,RxSwiftExt 是一个社区项目,它拥有不属于 RxSwift 本身的各种附加运算符,它有一个适合这种情况的运算符,称为 withUnretained
。它最初是由一位好朋友、iOS 演讲者 Vincent Pradeilles 实现的。
由于该运算符的受欢迎程度以及该用例的普遍性,因此将其引入 RxSwift 本身是有意义的。
从 RxSwift 6 开始,您可以像这样重写上面的代码:
swift
viewModel.importantInfo
.withUnretained(self) // Tuple of (Object, Element)
.subscribe(onNext: { owner, info in
owner.doImportantTask(with: info)
})
.disposed(by: disposeBag)
干净多了!
Infallible
Infallible
是一种新型流,与 Observable
相同,只有一个区别 - 它保证不会失败。这意味着你不能从中发出错误事件,这是由编译器保证的。
例如,您可以使用 Infallible.create
创建一个类似于 Observable.create
的对象:
swift
Infallible<String>.create { observer in
observer(.next("Hello"))
observer(.next("World"))
observer(.completed)
// No way to error here
return Disposables.create {
// Clean-up
}
}
请注意,您只能传递 .next(Element)
或 .completed
事件。你没有办法让这个可观察序列失败。所有其他围绕 Infallible
工作的运算符都具有相同的保证(例如,您不能调用 Infallible.error
而不是 Observable.error
)
如果您使用过 RxCocoa,您可能会想 --- 嘿,Driver
和 Signal
之间有什么区别?
首先,Infallible
位于 RxSwift 中,而另外两个位于 RxCocoa 中。但更重要的是,Driver
和 Signal
始终使用 MainScheduler
并共享资源(使用 share()
)。 Infallible
的情况并非如此,它完全是一个基础的可观察序列,仅具有编译时保证无误性的功能。
Observable<Data>
的新解码 (type:decoder:
) 运算符
RxSwift 6 添加了一个解码运算符,专门用于发出数据的 Observables,类似于合并:
swift
service.rx
.fetchJSONUsers() // Observable<Data>
.decode(type: [User].self, decoder: JSONDecoder()) // Observable<[User]>
可变参数 drive()
和 emit()
RxSwift 5 引入了可变参数绑定,它可以让您执行以下操作:
swift
viewModel.string.bind(to: input1, input2, input3)
RxSwift 6 现在为 Driver
和 Signal
带来了相同的可变参数绑定 - 使用可变参数驱动和发出操作符:
swift
viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)
Single
现在更好地遵循 Swift 的 Result
直到 RxSwift 5,Single
有一个自定义事件:
swift
public enum SingleEvent<Element> {
case success(Element)
case error(Swift.Error)
}
如果您看到这个并说,嘿 - 这看起来很像 Result<Element, Swift.Error>
,那么你是对的!
从 RxSwift 6 开始,SingleEvent
只是 Result<Element, Swift.Error>
的别名。
这一变化也体现在其他 API 中,例如 subscribe
:
swift
// RxSwift 5
single.subscribe(
onSuccess: { value in
print("Got a value: \(value)")
},
onError: { error in
print("Something went wrong: \(error)")
}
)
// RxSwift 6
single.subscribe(
onSuccess: { value in
print("Got a value: \(value)")
},
onFailure: { error in
print("Something went wrong: \(error)")
}
)
支持 keypath 的新的 uniqueUntilChange(at:)
运算符
uniqueUntilChanged
是一个超级有用的运算符,它可以让您删除相同的值排放以避免浪费地处理它们。
例如:
swift
myStream.distinctUntilChanged { $0.searchTerm == $1.searchTerm }
这是关键路径非常有用的另一种情况。从 RxSwift 6 开始,您可以简单地编写:
swift
myStream.distinctUntilChanged(at: \.searchTerm)
新的 ReplayRelay
Relay 围绕 Subject,让您以仅处理值的方式中继消息,因为中继保证永远不会失败或完成。
ReplayRelay
是 RxSwift 6 的最新补充,除了现有的 BehaviorRelay
和 PublishRelay
之外,它还包装了 ReplaySubject
。
创建一个使用与创建 ReplaySubject
完全相同的接口:
swift
// Subject
ReplaySubject<Int>.create(bufferSize: 3)
// Relay
ReplayRelay<Int>.create(bufferSize: 3)
新的 DisposeBag
函数构建器
RxSwift 6 包含一个新的 DisposeBag
函数构建器,用于类似 SwiftUI 的无逗号语法:
swift
var disposeBag = DisposeBag {
observable1.bind(to: input1)
observable2.drive(input2)
observable3.subscribe(onNext: { val in
print("Got \(val)")
})
}
// Also works for insertions
disposeBag.insert {
observable4.subscribe()
observable5.bind(to: input5)
}
很多很多(很多)运算符重命名
我们在 RxSwift 6 中花了一些时间,重命名了许多运算符,以便尽可能更好地遵守 Swift 的代码指南。
这是一个大部分完整的列表:
RxSwift 5 | RxSwift 6 |
---|---|
catchError(_:) |
catch(_:) |
catchErrorJustReturn(_:) |
catchAndReturn(_:) |
elementAt(_:) |
element(at:) |
retryWhen(_:) |
retry(when:) |
takeUntil(_:) |
take(until:) |
takeUntil(behavior:_:) |
take(until:behavior:) |
takeWhile(_:) |
take(while:) |
takeWhile(behavior:_:) |
take(while:behavior:) |
take(.seconds(3)) |
take(for: .seconds(3)) |
skipWhile(_:) |
skip(while:) |
observeOn(_:) |
observe(on:) |
subscribeOn(_:) |
subscribe(on:) |
对 XCFrameworks 更好的支持
RxSwift 6 的每个版本现在都会捆绑一组 XCFrameworks。
由于二进制模块的稳定性,这允许轻松链接到 RxSwift 的预构建副本,而无需担心升级到下一个版本的 Swift 时的前向兼容性。
总结
希望您喜欢 RxSwift 6 一些最有趣的功能和更新的快速概述,但这并不是所有问题都已修复。
有大量的错误修复、改进和小补充值得一看。请务必花点时间查看release notes。