LoginSignup
30
31

More than 5 years have passed since last update.

swift初心者がSmartNews風ニュースアプリを作ってみる過程を晒す(8) - RxSwiftを用いてMVVMを実装する

Last updated at Posted at 2016-07-03

はじめに

swiftはほとんど未経験ですが、SmartNews風ニュースアプリを作ってみて、その過程をさらしています。

スクリーンショット 2016-07-03 11.39.55.png

前回は、こんな記事を書きました。

swift初心者がSmartNews風ニュースアプリを作ってみる過程を晒す(7) - MVVMで関心事を分離する手法を学ぶ - Qiita

今回は、RxSwiftを用いて実際にMVVMアーキテクチャを使用した実装を行います。

RxSwiftを使用したMVVM

取得した記事を表示するシンプルなニュースアプリを作成します。
ニュース記事のデータは、ここから取得します。

Model

Entryモデルは一つ一つの記事を表現します。
JSONオブジェクトをparseし、各プロパティにセットすることで初期化されます。

Entry.swift
import Foundation
import RealmSwift

class Entry: Object {

    dynamic var title: String = ""
    dynamic var link: String = ""
    dynamic var contentSnippet: String = ""
}

ViewModel

ViewModelには以下の責務をもたせます。

  • ビジネスロジック(ネットワークリクエストを発生させるetc)
  • 表示に必要なデータをViewControllerに届ける
  • Modelの更新を監視する
EntriesViewModel.swift
import RxSwift
import RxCocoa

final class EntriesViewModel {

    //MARK: - Dependecies

    //MARK: - Model

    var entries: Driver<[Entry]> = Driver.never()

    //MARK: - Set up

    init() {
        //Initialise dependencies

    }

    func reloadData(title: String) {
        entries = EntryAPIService().fetchEntries(q: title)
    }
}

DriverはUIレイヤーを直感的にリアクティブプログラミングするための部品です。
Driverって何なんだ、ということに関しては、こちらに詳しく記載されています。

Units are totally optional. You can use raw observable sequences everywhere in your program and all RxCocoa APIs work with observable sequences.

公式ドキュメントにも記載があるように、使用しないと実装できないものでもないですが、便利なので使いましょう、という位置付けのようです。

Driverの特徴

Driverの特徴は以下の通りです。

  • エラーでストリームが終了しない
    • JSONのparseエラーやAPIとの通信失敗が起きた場合に、UIコンポーネントが更新できなくなるようなことを防ぎます
  • main schedulerでsubscribe, observeする
    • API通信の結果を使用して、バックグラウンドスレッドでUIコンポーネントを更新するようなことをするとCrashの原因になります
  • 副作用をシェアする
    • API通信の結果を複数のUIコンポーネントにバインドした時に、2回HTTPリクエストが発生するような事を回避します

公式ドキュメントでは、典型的なサンプルコードを交えて非常に詳しく解説されているので、一読をお勧めします :bow:

ViewController

TableViewController.swift

   @IBOutlet weak var tableView: UITableView!

    private let bag = DisposeBag()
    private var entryList: [Entry]  = []

    // create ViewModel
    let viewModel = EntriesViewModel()

    override func viewDidLoad() {

        super.viewDidLoad()
        self.tableView.registerNib(UINib(nibName: "EntryTableViewCell", bundle: nil), forCellReuseIdentifier: "EntryTableViewCell")

        viewModel.reloadData(self.title!) //1

        // see http://yannickloriot.com/2016/01/make-uitableview-reactive-with-rxswift/
        // bind articles to UITableView

        // If there is a `drive` method available instead of `bindTo`,
        // that means that the compiler has proven that all properties
        // are satisfied.
        viewModel.entries.drive(self.tableView.rx_itemsWithCellIdentifier("EntryTableViewCell")) { //2
            (index, entry: Entry, cell:EntryTableViewCell) in
            cell.updateCell(entry)
        }.addDisposableTo(bag) //3

        viewModel.entries.driveNext { [unowned self] in //4
            self.entryList = $0
        }.addDisposableTo(bag)

    }
  • 1. APIにアクセスして記事の取得を行っています。
  • 2. entriesの更新をトリガーにして、tableviewの更新を行っています。
  • 3. unsubscribeを自動的に行って、リソースを解放するための記述です。

Using dispose bags or takeUntil operator is a robust way of making sure resources are cleaned up. We recommend using them in production even if the sequences will terminate in finite time.

感想

  • 各クラスの責務が明確になり、仕様変追加時の変更箇所が明確になりました。
  • テストが書きやすくなりそうです。

おわりに

ソースコードはこちらです。

次回は、少し趣を変えて、実装したアプリケーションのテストコードを書いていきます。

参考

30
31
0

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
30
31