TodoAppでRxSwift入門[part4]


概要

最近RxSwiftを勉強し始めて現在理解していることを備忘録として残せたらいいなと思い記事にします。
そもそもRxSwiftのRxとは

Rx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。
Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。

とのことです。
詳しくは以下のサイトを参照してください。
入門!RxSwift
RxSwiftについてようやく理解できてきたのでまとめることにした(1)

今回はPart3になります。
前回の記事はこちら
TodoAPPでRxSwift入門[part3]

今回でうまく修まれば最後になります。

TodoListViewModel

TodoListViewModel.swift
// Cellに渡す時に扱いやすいようにしています。
typealias TodoItemsSection = SectionModel<Int, TodoCellViewPresentable>

protocol TodoListPresentable {

    typealias Input = ()
    typealias Output = (
        todos: Driver<[TodoItemsSection]>, ()
    )

    var input: TodoListPresentable.Input { get }
    var output: TodoListPresentable.Output { get }
}

class TodoListViewModel: TodoListPresentable {
    var input: TodoListPresentable.Input
    var output: TodoListPresentable.Output

    private let storeManager: StoreManager

    init(input: TodoListPresentable.Input, storeManager: StoreManager) {
        self.input = input
        self.storeManager = storeManager
        self.output = TodoListViewModel.output(storeManager: self.storeManager)
    }
}

private extension TodoListViewModel {
    static func output(storeManager: StoreManager) -> TodoListPresentable.Output {
        let todos = storeManager.fetchTodosFromFirestore()
            .map { $0.compactMap { TodoCellViewModel(usingModel: $0) } }
            .map { [TodoItemsSection(model: 0, items: $0)] }
            .asDriver(onErrorJustReturn: [])

        return (
            todos: todos, ()
        )
    }
}

前回と同じような構造ですね。コメントでも書いてある通り、

typealias TodoItemsSection = SectionModel<Int, TodoCellViewPresentable>

の部分は扱いやすくするためです。

続いてoutput関数ですがmap関数で型変換をしてあげます。
この時新しいObservableに作り変えられる?(表現があってるのかわからない)そうです。
compactMapはSwift標準のもので、配列内のnilを取り除いてくれます。
以下を参照してください。
Swift で map, compactMap, flatMap を使いこなそう

TodoListViewController

TodoListViewController
import UIKit
import RxSwift
import RxCocoa
import RxDataSources

class TodoListViewController: UIViewController, Storyboardable {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var dismissButton: UIBarButtonItem!

    private static let cellId = "TodoCell"

    private let dataSources = RxTableViewSectionedReloadDataSource<TodoItemsSection> { (_, tableView, indexPath, item) -> UITableViewCell in
        let cell = tableView.dequeueReusableCell(withIdentifier: TodoListViewController.cellId, for: indexPath) as! TodoCell
        cell.configure(usingViewModel: item)
        return cell
    }


    private let disposeBag = DisposeBag()
    private var viewModel: TodoListPresentable!
    private let storeManager = StoreManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.viewModel = TodoListViewModel(input: (), storeManager: storeManager)

        setupViews()
        setupBinding()
    }

    private func setupViews() {
        tableView.register(UINib(nibName: "TodoCell", bundle: nil), forCellReuseIdentifier: TodoListViewController.cellId)
        tableView.separatorStyle = .none
    }

    private func setupBinding() {
        self.viewModel.output.todos
            .drive(tableView.rx.items(dataSource: self.dataSources))
            .disposed(by: disposeBag)

        dismissButton.rx.tap.subscribe(onNext: { [weak self] in
            self?.dismiss(animated: true)
        }).disposed(by: disposeBag)
    }

}

コード自体はそこまで長くないですね。まずは以下のコードからみていきましょう。

private let dataSources = RxTableViewSectionedReloadDataSource<TodoItemsSection> { (_, tableView, indexPath, item) -> UITableViewCell in
        let cell = tableView.dequeueReusableCell(withIdentifier: TodoListViewController.cellId, for: indexPath) as! TodoCell
        cell.configure(usingViewModel: item)
        return cell
    }

RxTableViewSectionedReloadDataSourceRxDataSourcesが提供してくれているので導入しておいてください。
<TodoItemsSection>は先程のTodoListViewModelでtypealiasしたものですね。
指定なかったら

<SectionModel<Int, TodoCellViewPresentable>>

と書きます。

そしてitemTodoCellViewPresentableが流れてくるのでそれに準拠した型のものになります。

setupBingin関数の中も特に難しいことはないと思います。

まとめ

part分けする予定もなかった本記事ですが、長くなってしまいました。
また本記事では出てきていないclassなどがあると思いますが、解説は要らないレベルのものになっていますのでGithubをみてください。
github

書く前よりほんの少し理解が進んだかな?という印象です。
もっと勉強して完全に理解したになれるように頑張りたいですねw