tripleCC的技术博客

ʕ•̫͡•ʔ-̫͡-ʕ•͓͡•ʔ-̫͡-ʕ•̫͡•ʔ-̫͡-ʕ•͓͡•ʔ-̫͡-ʔ

关于RxSwift中的DisposeBag

在RxSwift中,订阅者都会返回一个Disposable(默认是Disposables),以便使用者可以在后续的操作中,取消此次订阅。
使用者可以调用dispose方法来进行取消订阅:

1
2
3
4
5
6
7
let disposables = Observable
    .just(1)
    .delay(2, scheduler: MainScheduler.instance)
    .subscribe { event in
        print(event)
    }
disposables.dispose()

关于手动取消订阅后,对应的subscribe回调会不会调用,官方的手册是这么说的:

  • 当scheduler是串行调度器,并且使用者在此调度器上调用了dispose,那么回调就不会执行[MainScheduler是在主线程/UI线程的串行调度器]
  • 其他情况都不能保证回调的执行与否[并行情况下,执行顺序无法保证]

所以官方并不推荐手动调用dispose,而是通过DisposeBag、takeUntil或者其他非手动调用dispose途径。 而且官方建议始终使用.addDisposableTo(disposeBag)来管理订阅,即使对于一般的订阅,这个操作是没有必要的。

关于DisposeBag,它的行为类似ARC,不过是由RxSwift进行管理的。它会在自身被销毁的时候,对添加到自身的Disposables手动调用dispose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private func dispose() {
    let oldDisposables = _dispose()

    for disposable in oldDisposables {
        disposable.dispose()
    }
}

private func _dispose() -> [Disposable] {
    _lock.lock(); defer { _lock.unlock() }

    let disposables = _disposables

    _disposables.removeAll(keepingCapacity: false)
    _isDisposed = true

    return disposables
}

deinit {
    dispose()
}

添加Disposables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public func insert(_ disposable: Disposable) {
    _insert(disposable)?.dispose()
}

private func _insert(_ disposable: Disposable) -> Disposable? {
    _lock.lock(); defer { _lock.unlock() }
    if _isDisposed {
        return disposable
    }

    _disposables.append(disposable)

    return nil
}

其内部用自旋锁处理多线程的安全问题。关于defer的OC版本,可以看objc-attribute-cleanup。ReactiveCocoa里也是有相应的@onExit实现。
要想DisposeBag中的所有Disposables执行dispose,只要赋一个新的值给disposeBag变量就可以了:

1
self.disposeBag = DisposeBag()

这样一来,原先的订阅都会被取消掉。

ReactiveCocoa的列表应用中,常常会看到这样的代码:

1
2
3
4
5
6
RAC(self, contentImageView.image) = [[[viewModel.contentImageSignal
    throttle:0.05]
    takeUntil:self.rac_prepareForReuseSignal]
    map:^id(RACTuple *value) {
        return [value first];
    }];

表示在cell复用时,取消对contentImageSignal的订阅。
RxSwift就可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var disposeBag: DisposeBag = DisposeBag()

......

viewModel.contentImage
    .throttle(0.05, scheduler: MainScheduler.instance)
    .bindTo(contentImageView.rx.image)
    .addDisposableTo(disposeBag)

......

public override func prepareForReuse() {
    super.prepareForReuse()
    self.disposeBag = DisposeBag()
}

由于cell有高复用性与重复性特点,所以关于响应式编程在cell中应用最好注意以下几点:

  • cell复用时[prepareForReuse]需要取消原先的事务
  • 为了避免因用户快速滑动界面,而产生大量创建事务与取消事务的动作,在cell刚进入可见区域时,不立刻执行事务
  • 限制事务的并发数

所以,一般一个界面中有非常多的cell时,对其中元素进行绑定最好加上throttle操作,以使界面更加流畅。


参考

Warnings
GettingStarted

Comments