RxSwift Binder を使ってみた
overview
RxSwift のBinderでSVProgressHUDを使ってみる。
Rxswift のgit hubによしなに書かれてる、サンプルがあり、slackでもそれを参考にしてくれと記載されている。
その部分を抜粋し、MVVMで使ってみた。
これまで、show と dismissを使ってやっていた箇所を簡単に記述することができる。
Sample code
ViewController が下記である。
//
// ViewController.swift
// RxDemo
//
// Created by Shichimitoucarashi on 2018/08/11.
// Copyright © 2018年 karadanote. All rights reserved.
//
import UIKit
import RxSwift
import RxCocoa
import SHColor
import SVProgressHUD
final class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label:UILabel!
@IBOutlet weak var button: UIButton!
@IBOutlet weak var indicator: UIActivityIndicatorView!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = ViewModel(tap: button.rx.tap.asObservable())
viewModel.indicator
.bind(to: SVProgressHUD().shared.rx.isShow)
.disposed(by: disposeBag)
viewModel.action
.subscribe()
.disposed(by: disposeBag)
}
}
extension Reactive where Base: UIActivityIndicatorView {
var isAnimating :Binder<Bool>{
return Binder(self.base) { indicator, flg in
if flg {
indicator.isHidden = false
indicator.startAnimating()
}else{
indicator.isHidden = true
indicator.stopAnimating()
}
}
}
}
extension Reactive where Base: SVProgressHUD {
var isShow: Binder<Bool> {
return Binder(self.base) { sv, flg in
if flg {
SVProgressHUD.show()
}else{
SVProgressHUD.dismiss()
}
}
}
}
private struct ActivityToken<E> : ObservableConvertibleType, Disposable {
private let _source: Observable<E>
private let _dispose: Cancelable
init(source: Observable<E>, disposeAction: @escaping () -> ()) {
_source = source
_dispose = Disposables.create(with: disposeAction)
}
func dispose() {
_dispose.dispose()
}
func asObservable() -> Observable<E> {
return _source
}
}
/**
Enables monitoring of sequence computation.
If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
public class ActivityIndicator : SharedSequenceConvertibleType {
public typealias E = Bool
public typealias SharingStrategy = DriverSharingStrategy
private let _lock = NSRecursiveLock()
private let _relay = BehaviorRelay(value: 0)
private let _loading: SharedSequence<SharingStrategy, Bool>
public init() {
_loading = _relay.asDriver()
.map { $0 > 0 }
.distinctUntilChanged()
}
fileprivate func trackActivityOfObservable<O: ObservableConvertibleType>(_ source: O) -> Observable<O.E> {
return Observable.using({ () -> ActivityToken<O.E> in
self.increment()
return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
}) { t in
return t.asObservable()
}
}
private func increment() {
_lock.lock()
_relay.accept(_relay.value + 1)
_lock.unlock()
}
private func decrement() {
_lock.lock()
_relay.accept(_relay.value - 1)
_lock.unlock()
}
public func asSharedSequence() -> SharedSequence<SharingStrategy, E> {
return _loading
}
}
extension ObservableConvertibleType {
public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<E> {
return activityIndicator.trackActivityOfObservable(self)
}
}
ViewModelを下記に記載する。
//
// ViewModel.swift
// RxDemo
//
// Created by Shichimitoucarashi on 2018/10/10.
// Copyright © 2018年 karadanote. All rights reserved.
//
import Foundation
import RxSwift
import RxCocoa
class ViewModel {
let action: Observable<Bool>
let indicator: Observable<Bool>
init(tap: Observable<Void>) {
let indicator = ActivityIndicator()
indicator = indicator.asObservable()
action = tap.flatMapLatest { _ in
return ViewModel.variable.observeOn(MainScheduler.instance)
.catchErrorJustReturn(false)
.trackActivity(indicator)
}
}
static var variable: Observable<Bool> {
return Observable.just(false).delay(1.0, scheduler: MainScheduler.instance)
}
}
今回の処理でもっとも重要なのは、trackActivityである、これがtracking してくれる便利な関数である。
下記に記したクラスが有効に働くことで、現在処理が走ってるか、走っていないかの判断を行うことができる。
ActivityIndicator <- このクラスが排他的制御を行なっている、
overview
処理が走ったら、インクリメントを行い、そうでない場合はデクリメントを行い、処理がひとつも走ってなければ、
falseを返し、一つでも走ってれば、trueを返すという仕組みであるので、処理が走ってれば、インジケーターを回し、そうでなければ、止めるという処理をワンライナーでかける、
最初はインジケーターをまわすのに、Bool値のプロパティーを作成するのはおかしいと感じましたが、
こういう使い方をするのでれば、これはこれで結構優れたものだと思える。
private struct ActivityToken<E> : ObservableConvertibleType, Disposable {
private let _source: Observable<E>
private let _dispose: Cancelable
init(source: Observable<E>, disposeAction: @escaping () -> ()) {
_source = source
_dispose = Disposables.create(with: disposeAction)
}
func dispose() {
_dispose.dispose()
}
func asObservable() -> Observable<E> {
return _source
}
}
/**
Enables monitoring of sequence computation.
If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
public class ActivityIndicator : SharedSequenceConvertibleType {
public typealias E = Bool
public typealias SharingStrategy = DriverSharingStrategy
private let _lock = NSRecursiveLock()
private let _relay = BehaviorRelay(value: 0)
private let _loading: SharedSequence<SharingStrategy, Bool>
public init() {
_loading = _relay.asDriver()
.map { $0 > 0 }
.distinctUntilChanged()
}
fileprivate func trackActivityOfObservable<O: ObservableConvertibleType>(_ source: O) -> Observable<O.E> {
return Observable.using({ () -> ActivityToken<O.E> in
self.increment()
return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
}) { t in
return t.asObservable()
}
}
private func increment() {
_lock.lock()
_relay.accept(_relay.value + 1)
_lock.unlock()
}
private func decrement() {
_lock.lock()
_relay.accept(_relay.value - 1)
_lock.unlock()
}
public func asSharedSequence() -> SharedSequence<SharingStrategy, E> {
return _loading
}
}
extension ObservableConvertibleType {
public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<E> {
return activityIndicator.trackActivityOfObservable(self)
}
}
上記実装方法に意見があるものはいつでもコメント欄に投稿していただけると幸いである。
ご連絡お待ちしております。
引用元