LoginSignup
122

More than 5 years have passed since last update.

RxSwiftでUIの更新にはDriverを使ってみたまとめ

Last updated at Posted at 2016-04-02

Driver

RxSwift/Documentation/Units.md #Driverにも記載がある通り、
UIレイヤーのリアクティブプログラミングのために提供されている部品 らしいと最近認識しました

以前記事にしたRxSwiftを使ったRSSFeed取得のサンプルでは
Viewの更新にも以下のようにVariable変数を使っていたのですが、

これまで
        // entriesはVariable変数
        viewModel.entries.asObservable()
            .subscribe(onNext: { [unowned self] x in
                // 更新
                self.refresh.endRefreshing()
                self.tableView.reloadData()
                }, onError: { error in
                    // エラー
                    // FIXME: いつかやるかも
                }, onCompleted: { () in
                    // 完了
                }) { () in

            }.addDisposableTo(disposeBag)

これってDriverを使うべきなのでは・・・?

ということで、Swiftビギナーな自分が四苦八苦しながらRxSwiftでUIの更新にはDriverを使ってみたまとめです

どんなアプリ?

:tada: : 以前作ったRSSFeedアプリのサンプルコードをリファクタリングしてみました

環境

  • Xcode 7.3
  • Swift 2.2
  • RxSwift 2.3.1
  • iOS8以上

リポジトリ

mafmoff/RxDriveRSSFeed

概要

過去の記事にあります

Driverを使う

イメージです

スクリーンショット 2016-04-01 19.04.43.png

Driverの使い所

1. Model : (準備) リクエスト結果をVariableに反映してViewModelに通知

ここでは、リクエストを実行し結果をSubscribeして各Variable値を更新します
これでViewModel側に各値の変更が通知されます
この時API通信はバックグランドで行うように指定しておきます

ListModel

    /// Load
    final let isLoading = Variable(false)
    /// Entries
    final let entries = Variable([Entry]())
    /// Error
    final let error: Variable<ErrorType?> = Variable(nil)
    /// Rx Dispose
    final let disposeBag = DisposeBag()

    final func request() {
        if isLoading.value {
            return
        }
        isLoading.value = true
        error.value = nil

        let request = FeedRequest()
        request.connect()
            // API接続はBackgroundを指定する
            .subscribeOn(SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Background))
            .subscribe(
                onNext: { [weak self] in self?.entries.value = $0.feed.entries },
                onError: { [weak self] in
                    self?.error.value = $0
                    self?.isLoading.value = false },
                onCompleted: { [weak self] in self?.isLoading.value = false }
            )
        .addDisposableTo(disposeBag)
    }

それぞれView側のUIと紐付きます

  • isLoading:UIRefreshControlの表示・非表示
  • entries:UITableView上に表示する一覧データ
  • error:エラー時用のUIViewの表示・非表示

2. ViewModel : モデルから受け取った値をDriverに変換する

リクエストで受け取った結果をDriverに変換します

:tada: TIPS: Driver.never()しておくと値を持たずCompletedすら発生しません
:tada: TIPS: Driverを使うにはimport RxCocoaが必要でした

ListViewModel

    /// Request Model class
    final private var model = ListModel()
    /// Entries
    final var entries = [Entry]()
    // Rx
    /// Data Updated
    final private(set) var dataUpdated: Driver<[Entry]> = Driver.never()
    /// Loading flg
    final private(set) var isLoading: Driver<Bool> = Driver.never()
    /// Error flg
    final private(set) var isError: Driver<Bool> = Driver.never()

    override init() {
        super.init()

        // ViewController側でTableViewの更新と紐付けます
        dataUpdated = Driver
            // エラーのとき、一覧に表示するエントリーを0件にするように変換しています
            .combineLatest(model.entries.asDriver(),
                           model.error.asDriver().map { $0 != nil },
                           resultSelector: { ($1) ? [] : $0 })
        // ViewController側でRefreshControlのステータスと紐付けます
        isLoading = model.isLoading.asDriver()
        // ViewController側でエラー画面の表示と紐付けます
        isError = model.error.asDriver().map { $0 != nil }
    }

1.のリクエストでVariableが更新されるたびに、Driverからはobserverに対して通知が行われます
ViewControllerではこの通知をトリガーにして、バインドしている各UIの更新を行います

3. ViewController : ViewModelのDriverをUIにバインドする

ViewDidLoad()内でDriverと各UIを紐付けます

ListController

        // TableView Reload
        viewModel.dataUpdated
            .driveNext { [unowned self] in
                self.viewModel.entries = $0
                self.tableView.reloadData()
            }
            .addDisposableTo(disposeBag)

        // Loading Status
        viewModel.isLoading
            .drive(refresh.rx_refreshing)
            .addDisposableTo(disposeBag)

        // Error View
        viewModel.isError
            .map { !$0 }
            .drive(errorView.rx_hidden)
            .addDisposableTo(disposeBag)

        // Pull Refresh
        refresh.rx_controlEvent(.ValueChanged)
            .subscribeNext { [unowned self] _ in self.viewModel.reloadData()
        }.addDisposableTo(disposeBag)


これで、リクエストをして結果が更新されるたびにDriverからはobserverに対して通知が行われ、バインドしている各UIの更新が働きました

できたもの

スクリーンショット 2016-04-02 14.46.48.png

まとめ

  • Driverはメインスレッド処理に適している
  • エラー発生しない

以上です :tada:
苦しみながら書きました!RxSwiftは難しいです・・(勉強会参加したかった)

Modelクラスなど、もっとかっこいい設計がありそうです。
今後もさらによい実装方法を勉強させて頂きます!
読んでいただき、ありがとうございました

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
122