0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TodoAPPでRxSwift入門[part2]

Last updated at Posted at 2021-03-02

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

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

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

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

#画面構成
Simulator Screen Shot - iPhone 11 - 2021-03-02 at 09.01.56.png

画面はこんな感じにします。
タイトルを入力するためのUITextFieldと詳細を入力するためのUITextViewとTodoを登録するためのUIButtonをスタックビューに入れてあげてます。

画面上部のWelcome to Simple Todo Appはただのデザインで入れてみただけなのでなくても大丈夫です。
下部のShow Todo Listは登録したTodoを見るためのボタンになっています。

UIの配置が面倒な人はこの記事が完結した時にgithubにあげるのでそこのStoryboardからコピペしてください。

#AddTodoViewModel
前回の記事の最後に予告したViewModelの作成に移ります。
そもそもViewModelとは、なんでもかんでもViewControllerに書いちゃったらViewControllerがめっちゃ長くなって見にくいしあまりよろしくないよねということからMVVMというアーキテクチャが編み出されてそれのVM(ViewModel)の部分に当たるものです。
詳しいことはググってください。

では早速コードのほうを書いていきましょう。

AddTodoViewModel.swift
import Foundation
import RxCocoa
import RxSwift

protocol AddTodoViewPresentable {
    typealias Input = (
        titleText: Driver<String>,
        detailText: Driver<String>
    )
    typealias Output = (
        isValid: Driver<Bool>, ()
    )
    
    var input: AddTodoViewPresentable.Input { get }
    var output: AddTodoViewPresentable.Output { get }
}

これで全部ではないですがとりあえずここまでで一旦解説を挟みます。
まずprotocol?と思われた方もいるかもしれないですがこれはプログラムを疎結合にするためです。疎結合とは?の方は

iOSアプリの基本設計を考える:疎結合の概念から構造化、MVVM、RxSwiftまで

を参照してください。

typealiasでInput(入力を受けとる)とOutput(受け取った値を変換したりして返す)を作っています。ちなみにtappleは要素が一つだけだとエラーになるのでOutputにはisValid()にしています。
次にDriverの説明をします。
Driverとは
・エラーを流さない
・メインスレッドでの通知が保証される
・shareReplayLatestWhileConnectedを使ったCold-Hot 変換
の特徴を持ったRxSwift(RxCocoa)独自のものらしいです。

今度はAddViewModelの中身です。

AddTodoViewModel.swift

class AddTodoViewModel: AddTodoViewPresentable {
    var input: AddTodoViewPresentable.Input
    var output: AddTodoViewPresentable.Output
    
    var storeManager: StoreManager
    
    init(input: AddTodoViewPresentable.Input, storeManager: StoreManager) {
        self.input = input
        self.output = AddTodoViewModel.output(input: self.input)
        self.storeManager = storeManager
    }
}

private extension AddTodoViewModel {
    static func output(input: AddTodoViewPresentable.Input) -> AddTodoViewPresentable.Output {
        let titleObservable = input.titleText.asObservable()
        let detailObservable = input.detailText.asObservable()
        
        let isValid = Observable.combineLatest(titleObservable, detailObservable) { (title, detail) -> Bool in
            return !title.isEmpty && !Validator.removeSpaceAndNewLine(text: detail).isEmpty
        }.asDriver(onErrorJustReturn: false)
        
        return (
            isValid: isValid, ()
        )
    }
}

extension AddTodoViewModel {
    func insertTodoToFireStore(title: String, detail: String) {
        storeManager.insertTodoToFireStore(title: title, detail: detail)
    }
}


ここはそこまで説明いらないかなと思いますが、 ViewModelを先程のPresentableに準拠させていないとエラーになるのでfixでもおしておいてください。
outputを初期化するためのoutput関数ですが外部から利用されないようにするためと読みやすくするためにprivate extensionで拡張します。
また、staticがついているのは、ついていないと変数のoutputが初期化されていないのにそんなの使えないよと怒られるのでこうしています。
initでクロージャでもいいとは思いますがそれだとinitが長くなってしまうので今のところこれがベストかと思いこうしています。

output関数の中を少し解説します。
inputで受け取った値をasObservable()でObservableに変えています。

そしてcombineLatest(observable1, observable2)

(違う型でも可の)直近の最新値同士を組み合わせたイベントを作ります。

引数に指定した値のどちらかがが変化すれば{}ないの処理を実行する感じですかね?

あと、さらっとValidatorというのが出ていますがこれはdetailが空白や改行のみだとfalseを返します。
コードはこちらです。

Validator.swift
import Foundation

class Validator {
    
    static func removeSpaceAndNewLine(text: String) -> String {
        var removeText = text.trimmingCharacters(in: .init(charactersIn: " "))
        removeText = removeText.trimmingCharacters(in: .init(charactersIn: " "))
        removeText = removeText.trimmingCharacters(in: .init(charactersIn: "\n"))
        return removeText
    }
    
}

Stringを返していますがBoolを返せばよかったと思います。

#まとめ
正直あまり理解できていないところもあり、まだまだ勉強しないといけないことだらけで泣きそうです。
疎結合なプログラムも連鎖的なバグを防ぐためなのはなんとなく理解できていますが、その恩恵を得られるほどのプログラムを書いた経験がないのでパッとしないです。
もっと勉強して就職して恩恵を感じたいですね。
次回はViewControllerになります。

TodoAPPでRxSwift入門[part3]

0
2
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?