ReSwiftを使うときに知っておくといいTips3選
前書き
最近AndroidでもiOSでもReduxアーキテクチャが気に入っており、アプリ開発時に(ごり押して)採用しているUroteaでございます。
Android向けReduxの記事も書いているので、Androidも書く人は読んでみてください。
ReSwiftを使ってアプリを書いていて気が付いたTips集
機会があり、iOSアプリを書くことになりました。
ログインしたユーザの権限によってボタンを非表示にしたり、レイアウトを変えたりする必要があるため、状態管理に強いReSwiftを選択しました。
ある程度作り終わった今、この選択は正解だったと思っています。
また、ViewとのbindingのためにRxSwiftも併用しましたが、こちらも採用してよかったと思っています。
そんな中で気が付いたTipsを共有したいと思います。
この記事で書くこと
- ReSwiftを使うときに、知っておくと便利なこと
- RxSwiftとどう連携したか
この記事で書かないこと
- ReSwiftの概念、使い方
- RxSwiftの概念、使い方
対象読者
- iOS開発の基本を知っている
- Swiftがある程度読める
- Reduxの基本を知っている
- ReSwiftの基本は知っている
- RxSwiftについてなんとなく知っている
- MVVMについてなんとなく知っている
Tips1: Actionはenumで定義する
型で区切ることで管理を容易にする
Reduxの概念の一つAction
ですが、ReSwiftのreadmeには以下のように書かれています。
struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}
もちろんこれでもいいのですが、Action
が100個くらいになると、どのAction
がどのUIのイベントに対応しているのかの管理が困難になります。と言うか無理です。
ある程度なんらかの単位に切り分けて管理しなければ私の脳では処理ができませんでした。
私は画面ごとに分割することにしました(分割単位や粒度は場合によると思います)。
enum Actions: Action {
case loginViewActions(LoginViewActions)
case mainViewActions(MainViewActions)
}
enum LoginViewActions {
case loginButtonTapped
case userNameTextChanged(userName: String)
}
enum mainViewActions {
// 省略
}
このように分割すると
- それぞれのAction
がどの画面に対応しているのか型で判別可能
- Action
の名前衝突問題を気にしなくて良い
Reducerでパターンマッチングが効く
さらに、Reducer
でも力を発揮します。公式のreadmeでは以下のように書かれています
func counterReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState()
switch action {
case _ as CounterActionIncrease:
state.counter += 1
case _ as CounterActionDecrease:
state.counter -= 1
default:
break
}
return state
}
こちらの欠点としては、新たなAction
を追加したときに、Reducer
に変更を加えるのを忘れてもコンパイルエラーにならないことです。
Reducer
のテストは書くと思うので、大丈夫だと思いますが気が付けるに越したことはありません。
enum
を採用すると以下のようになります。
func counterReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState()
switch action {
case loginViewActions(let action):
switch action {
case loginButtonTapped:
// なんらかの処理
case userNameTextChanged(let userName):
// なんらかの処理
}
case mainViewActions(let action):
switch action {
// 省略
}
}
}
enum
にすることでdefault
句を消すことができ、新しいAction
を増やすとコンパイルで弾かれるようになります。
また、どの部分でどのAction
の処理をしているのかを見やすくなるので、可読性が向上します。
Tips2: RxSwiftのdistinctUntilChangedを使用してViewに状態の変更を伝える
RxSwiftにはRxCocoaというViewと連携するときに便利なライブラリがついており、これを活用します。
今回はMVVMアーキテクチャを採用した場合のサンプルです。
class LoginViewModel {
private let loginButtonEnabledOutputStream = PublishRelay<Bool>
let loginButtonEnabled: Signal<Bool> {
self.loginButtonEnabledOutputStream.asSignal().distinctUntilChanged()
}
}
extension LoginViewModel: StoreSubscriber {
typealias StoreSubscriberStateType = AppState
func newState(state: AppState) {
self.loginButtonEnabledOutputStream(state.login.buttonEnabled)
}
}
ポイントはdistinctUntilChanged()
です。ReSwiftのstateはstateが更新されるたびに呼び出されます。
上記の例ですと、loginButton
の状態が更新されていなくても、state全体としては更新されているとnewState(state: AppState)
が呼び出されます。
ボタンをenable
からenable
にするなら問題はありませんが、dialog
を表示する処理などをReSwiftに乗っけると、何かあるたびにdialog
が表示されてしまいます。
distinctUntilChanged()
を挟んでおくことで、false -> trueなど、状態が変化した時のみViewに通知されるようになります。
Tips3: stateのstructにcopy()を実装しておく
Kotlinを触ったことがある人向けに説明すると、data class
のcopyメソッドです。
触ったことがない人向けに説明すると、以下のようなメソッドです。
let loginState = LoginState(/*省略*/)
let newLoginState = loginState.copy(loginButton: true)
// newLoginStateはloginStateのloginButtonだけtrueにし、他のフィールドは全て同じ値のstruct
ReduxのReducer
では前のstateから新しいstateを生み出すコードを書きます。
しかし、状態が複雑な場合、前の状態から新しい状態を全てについて考えていると脳がパンクします。
copyメソッドを使用することで、前の状態との差分のみ更新することができるようになり、脳のメモリが節約できます。
func counterReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState()
switch action {
case loginViewActions(let action):
switch action {
case loginButtonTapped:
// ログインボタンがタップされたので、押せなくする
return state.copy(loginButton: false)
}
}
}
Swiftにはcopy()を自動的に作ってくれる機構はないので、頑張って書くしかありません。
私は以下のように書きました。
struct AppState {
let loginButton: Bool
let loginUserName: String?
func copy(
loginButton: Bool? = nil,
loginUserName: String?? = nil
) {
return AppState(
loginButton = loginButton ?? self.loginButton,
loginUserName = loginUserName ?? self.loginUserName
)
}
こうすることで、copyメソッドで指定しなかった値はcopy元の値が使用されるようになります。
唯一の弊害として、明示的にnil
を入れたい場合は以下のようにしなければなりません。
普通にnil
を代入するとcopy元の値が使用されてしまう!!
let state = AppState(/*省略*/)
state.copy(loginUserName: .some(nil)) // 明示的にloginUserNameにnilを入れてcopyする方法
ちょっと気をつけて使う必要がありますが、とても便利でした。ただ、copyメソッドを書くのが面倒です...
まとめ
iOSアプリをReSwiftを活用してがっつり書いてみて学んだTips3選でした。
- Actionはenumで定義する
- RxSwiftと連携する場合はdistinctUntilChanged()を活用する
- stateにcopyメソッドを実装する
書き始める前に知っているだけで簡単に導入できると思うので、ぜひご活用ください。
Author And Source
この問題について(ReSwiftを使うときに知っておくといいTips3選), 我々は、より多くの情報をここで見つけました https://qiita.com/Urotea/items/ecbea0a498295c5c9261著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .