Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした