#概要
最近RxSwiftを勉強し始めて現在理解していることを備忘録として残せたらいいなと思い記事にします。
そもそもRxSwiftのRxとは
Rx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。
Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。
とのことです。
詳しくは以下のサイトを参照してください。
入門!RxSwift
RxSwiftについてようやく理解できてきたのでまとめることにした(1)
今回はPart2になります。
前回の記事はこちら
TodoAPPでRxSwift入門[part1]
画面はこんな感じにします。
タイトルを入力するためのUITextField
と詳細を入力するためのUITextView
とTodoを登録するためのUIButton
をスタックビューに入れてあげてます。
画面上部のWelcome to Simple Todo App
はただのデザインで入れてみただけなのでなくても大丈夫です。
下部のShow Todo List
は登録したTodoを見るためのボタンになっています。
UIの配置が面倒な人はこの記事が完結した時にgithubにあげるのでそこのStoryboardからコピペしてください。
#AddTodoViewModel
前回の記事の最後に予告したViewModel
の作成に移ります。
そもそもViewModel
とは、なんでもかんでもViewControllerに書いちゃったらViewControllerがめっちゃ長くなって見にくいしあまりよろしくないよねということからMVVMというアーキテクチャが編み出されてそれのVM(ViewModel)の部分に当たるものです。
詳しいことはググってください。
では早速コードのほうを書いていきましょう。
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
?と思われた方もいるかもしれないですがこれはプログラムを疎結合にするためです。疎結合とは?の方は
を参照してください。
typealiasでInput
(入力を受けとる)とOutput
(受け取った値を変換したりして返す)を作っています。ちなみにtapple
は要素が一つだけだとエラーになるのでOutput
にはisValid
と()
にしています。
次にDriver
の説明をします。
Driverとは
・エラーを流さない
・メインスレッドでの通知が保証される
・shareReplayLatestWhileConnectedを使ったCold-Hot 変換
の特徴を持ったRxSwift(RxCocoa)独自のものらしいです。
今度はAddViewModelの中身です。
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
を返します。
コードはこちらです。
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になります。