RxSwift 1.特征

文章目录
  1. 1. 特征是如何工作的
  2. 2. 可观察序列
    1. 2.1. RxSwift 特征 Single/Completable/Maybe
      1. 2.1.1. Single
      2. 2.1.2. Completable
      3. 2.1.3. Maybe
    2. 2.2. RxCocoa 特征 Driver/Signal/ControlEvent
      1. 2.2.1. Driver
      2. 2.2.2. Signal
      3. 2.2.3. ControlProperty
      4. 2.2.4. ControlEvent

特性完全是可选的。你可以自由地在程序中的任何地方使用原始的Observable序列,因为所有核心RxSwift / RxCocoa API都支持它们。

Traits

特征是如何工作的

特性只是 Observable sequence 的包装器结构。

你可以将它们视为可观察序列的一种构建器模式实现。构建特质后,调用.asObservable() 会将其转换回原始的可观察序列。

1
2
3
4
5
6
7
struct Single<Element> {
let source: Observable<Element>
}

struct Driver<Element> {
let source: Observable<Element>
}

可观察序列

Observable Sequence: 它是信号源,产生事件,RxSwift 重视 Observable<T> 类。

下面介绍的几个类型都是在 Observable<T> 类的基础上定制化出来的特征序列。

Observable<T> 可发送出来的事件类型

1
2
3
4
5
public enum Event<Element> { 
case next(Element)
case error(Swift.Error)
case completed
}

RxSwift 特征 Single/Completable/Maybe

Single

  1. Single 只能发送两个事件
1
2
3
4
5
6
public enum SingleEvent<Element> {
/// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)
case success(Element)
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
}
  1. 在 Single 的初始化中可以看到做了 SingleEvent -> Event 的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static func create(subscribe: @escaping (@escaping SingleObserver) -> Disposable) -> Single<Element> {
let source = Observable<Element>.create { observer in
return subscribe { event in
switch event {
case .success(let element):
observer.on(.next(element))
observer.on(.completed)
case .error(let error):
observer.on(.error(error))
}
}
}
return PrimitiveSequence(raw: source)
}
  1. .success 事件中,Single 发送了一个 .next 后紧跟着又发送了 .completed 事件,所以:
  • success - 产⽣⼀个单独的元素
  • error - 产⽣⼀个错误
  • 不会共享附加作用
  1. Single 实际场景:

Single 的例⼦就是执⾏ HTTP 请求,然后返回⼀个应答或错误。不过你也可以⽤ Single 来描述 任何只有⼀个元素的序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func getRepo(_ repo: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
if let error = error {
single(.error(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.error(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}

订阅:

1
2
3
4
5
6
7
8
9
10
getRepo("ReactiveX/RxSwift")
.subscribe { event in
switch event {
case .success(let json):
print("JSON: ", json)
case .error(let error):
print("Error: ", error)
}
}
.disposed(by: disposeBag)

or

1
2
3
4
5
6
7
8
getRepo("ReactiveX/RxSwift")
.subscribe(onSuccess: { json in
print("JSON: ", json)
},
onError: { error in
print("Error: ", error)
})
.disposed(by: disposeBag)
  1. 可以对 Observable 调⽤ .asSingle() ⽅法,将它转换为 Single。

Completable

  1. Completable 也只能发送两个事件,他没有 .next 事件,所以他们有接受数据值相关的事件!
1
2
3
4
5
6
public enum CompletableEvent {
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
  1. Completable 创建方法同 Single,也是做了 CompletableEvent -> Event 的处理
  • 没有 next 事件,不会发出元素
  • 只发送 completed 或者 error
  • 不会共享附加作⽤
  1. Completable 实际场景:

Completable 适⽤于那种你只关⼼任务是否完成,⽽不需要在意任务返回值的情况。它和 Observable有点相似。

1
2
3
4
5
6
7
8
9
10
11
12
func cacheLocally() -> Completable {
return Completable.create { completable in
// Store some data locally
...
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create {}
}
completable(.completed)
return Disposables.create {}
}
}

订阅

1
2
3
4
5
6
7
8
9
10
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print("Completed with no error")
case .error(let error):
print("Completed with an error: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)

or

1
2
3
4
5
6
7
8
cacheLocally()
.subscribe(onCompleted: {
print("Completed with no error")
},
onError: { error in
print("Completed with an error: \(error.localizedDescription)")
})
.disposed(by: disposeBag)

Maybe

  1. 它介于 Single 和 Completable 之间,它要么只能发出⼀个元素,要么产⽣⼀个 completed 事件,要么产⽣⼀个 error 事件。
1
2
3
4
5
6
7
8
public enum MaybeEvent<Element> {
/// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)
case success(Element)
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
  1. Maybe 初始化方法,做了 MaybeEvent -> Event 的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static func create(subscribe: @escaping (@escaping MaybeObserver) -> Disposable) -> PrimitiveSequence<Trait, Element> {
let source = Observable<Element>.create { observer in
return subscribe { event in
switch event {
case .success(let element):
observer.on(.next(element))
observer.on(.completed)
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
}
return PrimitiveSequence(raw: source)
}
  1. 使用场景

  2. Observable 调⽤ .asMaybe() ⽅法,将它转换为 Maybe。

RxCocoa 特征 Driver/Signal/ControlEvent

Driver

  1. 主要是为了简化 UI 层的代码。不过如果你遇到的序 列具有以下特征,你也可以使⽤它:
  • 不会产⽣ error 事件
  • ⼀定在 MainScheduler 监听(主线程监听)
  • 共享附加作⽤
  1. 为什么要叫 Driver 呢?

它的目的是让 model 数据层驱动 UI 变化。

E.g.

  • Drive UI from CoreData model.
  • Drive UI using values from other UI elements (bindings). …

初学者会使用这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let results = query.rx.text
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
}

results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)

results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)

上面的代码会有什么问题?

  • 如果 fetchAutoCompleteItems observable sequence 发出 error (eg连接失败,解析错误), 这样的错误不会解绑订阅,UI再也不会响应新的查询
  • 如果 fetchAutoCompleteItems 返回的结果在某个后台线程,后台线程返回的结果会绑定到 UI元素,导致确定性的 crashs
  • 结果绑定到两个 UI 元素上,意味着每次用户查询都会发送 2次 HTTP 请求,这不是开发者的本意

该代码的更合适版本如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let results = query.rx.text
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.observeOn(MainScheduler.instance)
// results are returned on MainScheduler
.catchErrorJustReturn([])
// in the worst case, errors are handled
}
.share(replay: 1)
// HTTP requests are shared and results replayed
// to all UI elements

results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)

results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)

确保在大型系统中正确处理所有需求很具有挑战性,但是有一种更简单的方法,使用编译器和特征来证明满足这些要求。

以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let results = query.rx.text.asDriver()
// This converts a normal sequence into a `Driver` sequence.
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: [])
// Builder just needs info about what to return in case of error.
}

results
.map { "\($0.count)" }
//Driver 的订阅要使用 drive来做
.drive(resultCount.rx.text)
// If there is a `drive` method available instead of `bind(to:)`,
.disposed(by: disposeBag)
// that means that the compiler has proven that all properties
// are satisfied.
results
.drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)

所以上面代码发生了什么?

  1. asDriver方法将ControlProperty特性转换为Driver特性。
1
query.rx.text.asDriver()

注意,没有什么特别的事情要做。
Driver 具有 ControlProperty 特性的所有属性,以及其他一些属性。底层的 observable sequence 只是包装为Driver trait,仅此而已。

  1. observable sequence 转化为 Driver
1
.asDriver(onErrorJustReturn: [])

P.S. 任何 observable sequence 转化为 Driver 都必须满足下面三点

  • Can’t error out.
  • Observe on main scheduler.
  • Sharing side effects (share(replay: 1, scope: .whileConnected)).

那么,如何确保满足这些呢?只需使用普通的Rx运算符即可。asDriver(onErrorJustReturn:[])等效于以下代码。

1
2
3
4
5
6
let safeSequence = xs
.observeOn(MainScheduler.instance) // observe events on main scheduler
.catchErrorJustReturn(onErrorJustReturn) // can't error out
.share(replay: 1, scope: .whileConnected) // side effects sharing

return Driver(raw: safeSequence) // wrap it up

最后一步是使用 drive ,而不是使用 bind(to:)

Signal

  1. Signal 和 Driver 相似,唯⼀的区别是,Driver 会对新观察者回放(重新发送)上⼀个元素,⽽ Signal 不会对新观察者回放上⼀个元素。
  • 不会产⽣ error 事件
  • 在 MainScheduler 监听(主线程监听)
  • 共享附加作⽤
  1. Signal 和 Driver 的区别示例
1
2
3
4
5
6
7
8
9
let textField: UITextField = ... 
let nameLabel: UILabel = ...
let nameSizeLabel: UILabel = ...
let state: Driver<String?> = textField.rx.text.asDriver()
let observer = nameLabel.rx.text
state.drive(observer)
// ... 假设以下代码是在⽤户输⼊姓名后运⾏
let newObserver = nameSizeLabel.rx.text
state.map { $0?.count.description }.drive(newObserver)

这个例⼦是将⽤户输⼊的姓名绑定到对应的标签上。当⽤户输⼊姓名后,我们创建了⼀个新的观察者,⽤于订阅姓名的字数。
那么问题来了,订阅时,展示字数的标签会⽴即更新吗? 因为 Driver 会对新观察者回放上⼀个元素(当前姓名),所以这⾥是会更新的。在对他进⾏订阅时,标签的默认⽂本会被刷新。这是合理的。

那如果我们⽤ Driver 来描述点击事件呢,这样合理吗?

1
2
3
4
5
6
7
8
let button: UIButton = ... 
let showAlert: (String) -> Void = ...
let event: Driver<Void> = button.rx.tap.asDriver()
let observer: () -> Void = { showAlert("弹出提示框1") }
event.drive(onNext: observer)
// ... 假设以下代码是在⽤户点击 button 后运⾏
let newObserver: () -> Void = { showAlert("弹出提示框2") }
event.drive(onNext: newObserver)

当⽤户点击⼀个按钮后,创建⼀个新的观察者,来响应点击事件。此时会发⽣什么?
Driver 会把上⼀次的点击事件回放给新观察者。所以,这⾥的 newObserver 在订阅时,就会接受到上次的点击事件,然后弹出提示框。这似乎不太合理。

于是我们就引⼊了 Signal:

1
2
3
4
5
6
7
...
let event: Signal<Void> = button.rx.tap.asSignal()
let observer: () -> Void = { showAlert("弹出提示框1") }
event.emit(onNext: observer)
// ... 假设以下代码是在⽤户点击 button 后运⾏
let newObserver: () -> Void = { showAlert("弹出提示框2") }
event.emit(onNext: newObserver)

在同样的场景中,Signal 不会把上⼀次的点击事件回放给新观察者,⽽只会将订阅后产⽣的点击事件,发布给新观察者。这正是我们所需要的。

  1. 结论: ⼀般情况下状态序列我们会选⽤ Driver 这个类型,事件序列我们会选⽤ Signal 这个类型。

ControlProperty

  1. 它是 Observable / ObservableType 特性,表示UI元素的属性。

值序列仅表示初始控制值和用户启动的值更改。程序化价值的变化将不会报告。

  1. 它的特点:

    • 永远不会失败
    • share(replay: 1)共享最新值
      • 它是有状态的,一旦订阅如果有产生过值,那么最新的值会立即重播
    • 当控件销毁的时候发送 complete 事件
    • 不发送 error 事件
    • 在主线程上传播 events
  2. 实际使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension Reactive where Base: UISearchBar {
/// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
let source: Observable<String?> = Observable.deferred { [weak searchBar = self.base as UISearchBar] () -> Observable<String?> in
let text = searchBar?.text
return (searchBar?.rx.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) ?? Observable.empty())
.map { a in
return a[1] as? String
}
.startWith(text)
}
let bindingObserver = Binder(self.base) { (searchBar, text: String?) in
searchBar.text = text
}
return ControlProperty(values: source, valueSink: bindingObserver)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension Reactive where Base: UISegmentedControl {
/// Reactive wrapper for `selectedSegmentIndex` property.
public var selectedSegmentIndex: ControlProperty<Int> {
return value
}

/// Reactive wrapper for `selectedSegmentIndex` property.
public var value: ControlProperty<Int> {
return UIControl.rx.value(
self.base,
getter: { segmentedControl in
segmentedControl.selectedSegmentIndex
}, setter: { segmentedControl, value in
segmentedControl.selectedSegmentIndex = value
}
)
}
}

ControlEvent

  1. 它是 Observable / ObservableType 特性,表示UI元素的事件。

  2. 特点:

    • 永不失败
    • 订阅的时候不会发送初试值
    • 当控件销毁时,发送 complete
    • 不发送 errors 事件
    • 在主线程发送 events
  3. 实际使用案例

这是一个典型的示例,你可以在开发中使用它:

1
2
3
4
5
6
7
public extension Reactive where Base: UIViewController {
/// Reactive wrapper for `viewDidLoad` message `UIViewController:viewDidLoad:`.
public var viewDidLoad: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in }
return ControlEvent(events: source)
}
}

在UICollectionView + Rx中,我们可以通过以下方式找到它:

1
2
3
4
5
6
7
8
9
10
extension Reactive where Base: UICollectionView {
/// Reactive wrapper for `delegate` message `collectionView:didSelectItemAtIndexPath:`.
public var itemSelected: ControlEvent<IndexPath> {
let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:)))
.map { a in
return a[1] as! IndexPath
}
return ControlEvent(events: source)
}
}

引用:

Traits