現状
isowordsのOnChange
を使っている。
var body: some ReducerProtocol<State, Action> {
core
.onChange(of: \.errorMessage) { message, _, _ in
if message == nil {
return .none
} else {
return .run { send in
await send(.setErrorMessageNil)
}
}
}
}
@ReducerBuilderOf<AppCore>
var core: some ReducerProtocol<State, Action> {
Reduce { state, action in
// ...
}
}
初期
TCAではStateが変わらないとVCにStateが流れてこないので、同じエラーメッセージを出したい時などは一回エラーメッセージにnilをセットする必要があります。
TCAよく分かってなかった初期の頃は(今も特に分かってない)、絶対もっと良い方法あるだろうなーと思いつつVCでアラートのボタンがタップされた時にsetErrorMessageNil
のようなActionを発行していました。
struct AppCore: ReducerProtocol {
struct State: Equatable {
var errorMessage: String?
}
enum Action: Equatable {
case setErrorMessageNil
}
var body: some ReducerProtocol<State, Action> {
switch action {
case .setErrorMessageNil:
state.errorMessage = nil
return .none
}
}
}
ViewController
/// アラートのOKがタップされた時に呼ぶメソッド
func didTapOk() {
store.send(.setErrorMessageNil)
}
中期
何か良い方法がないかとサンプルを探してたらExampleに下記のようなコードがありました。
case .rainbowButtonTapped:
return .run { send in
for color in [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .black] {
await send(.setColor(color), animation: .linear)
try await self.clock.sleep(for: .seconds(1))
}
}
.cancellable(id: CancelID.self)
こういう書き方もできるのかーと思って早速導入しました。
struct AppCore: ReducerProtocol {
struct State: Equatable {
var errorMessage: String?
}
enum Action: Equatable {
case apiResponse(Result<APIResponse, CustomError>)
case setErrorMessageNil
}
var body: some ReducerProtocol<State, Action> {
switch action {
case .apiResponse(.failure(let error)):
state.errorMessage = error.description
return .run { send in
await send(.setErrorMessageNil)
}
case .setErrorMessageNil:
state.errorMessage = nil
return .none
}
}
}
VC上でActionを発行する必要が無くなりコードがシンプルになりました。
今
Actionが増えてerrorMessageを処理する箇所が増えたり、他のStateが増えていくにつれて「毎回.run
書くの面倒だな...」と思い始めまたサンプルを探していたらOnChangeを発見しました。
.onChange(of: { $0.gameOver == nil }) { _, _, _ in
.fireAndForget {
await Task.cancel(id: CubeShakingID.self)
for music in AudioPlayerClient.Sound.allMusic where music != .gameOverMusicLoop {
await self.audioPlayer.stop(music)
}
}
}
最初は単純にonChangeでActionを発行していたのですが、nilチェックしないとActionが複数回発行されてしまいテスト書く時に想定してない挙動をするので注意です(やりました)
// ❌ これだとStateにnilがセットされた時にまたonChangeが呼ばれて無駄にActionが発行されることになる
.onChange(of: \.errorMessage) { _, _, _ in
return .run { send in
await send(.setErrorMessageNil)
}
}
// ⭕ nilチェックをすれば無駄なactionが発行されない
.onChange(of: \.errorMessage) { message, _, _ in
if message == nil {
return .none
} else {
return .run { send in
await send(.setErrorMessageNil)
}
}
}
ライセンス的にどうなのかと思ってDeepLで翻訳してみたら
- ソフトウェア全体(isowordsアプリ自体)を複製、配布できるのは非商用の学習、開発、研究、教育目的のみ
- コードの一部であれば競合でない限り商用利用可能
というふうに読み取れたのでisowords的なパズルゲームを作らない限り商用利用でも使って問題なさそうな気がするけどどうでしょう。
ライセンスに詳しい方。
コメントください。
TCA使ってみて
pros
- テストが書きやすい。
想定してないアクションが発行されてる、Effect(API)の呼び出しが完了してないなどがテストで検知できるから便利だなーって思いました。
cons
- 情報が少なくて正しい書き方、プラクティスみたいなのがいまいち分からない。(Swift TCAのサンプルコード探しまくることになる)
- 困った時にググってブログ記事やStack Overflowで解決...みたいなことがあまりできない。
- 1.0に達してないのでインターフェースがまた変わることがあるかもしれない(?)