Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Overviewing TCA v1.7 & Back and forth with MVVM

Fumiya Sakai
February 07, 2024

Overviewing TCA v1.7 & Back and forth with MVVM

potatotips#86での登壇資料になります。

The Composable Architecture(以下、TCAと記載します)に触れた経験から感じた事やReduxを利用した経験から、TCA自体をあまり知らない状態からキャッチアップする際のヒントやReduxとの共通点・相違点を見比べる際のポイント等についてまとめた物になります。

また、最新バージョンのTCAに関するドキュメントやサンプルコードに触れた際の所感についても簡単ではありますがご紹介しています。

Fumiya Sakai

February 07, 2024
Tweet

More Decks by Fumiya Sakai

Other Decks in Technology

Transcript

  1. 自己紹介 ・Fumiya Sakai ・Mobile Application Engineer アカウント: ・Twitter: https://twitter.com/fumiyasac ・Facebook:

    https://www.facebook.com/fumiya.sakai.37 ・Github: https://github.com/fumiyasac ・Qiita: https://qiita.com/fumiyasac@github 発表者: ・Born on September 21, 1984 これまでの歩み: Web Designer 2008 ~ 2010 Web Engineer 2012 ~ 2016 App Engineer 2017 ~ Now iOS / Android / sometimes Flutter
  2. 今回のスライドにつきまして 個人的にTCAに関連するトピックスや実際に軽く触れてみた際の所感を伝えたい 僕自身もTCAの熟練度についてはそれ程高くはないのでお手柔らかにお願いします。 1. 以前にも業務や個人活動の中でReduxを利用した経験があり以前からも関心はあった: Reduxの機構を利用したアーキテクチャを利用した開発経験を通じて、特に複雑かつ構成要素が複雑な入力を伴う画面等において は力を発揮する場面もありました。Reduxの考え方を採用しているTCAの動向は個人的にも気になっています。 2. MVVM ⇔

    TCA or Reduxの処理を置き換える経験をした際に感じてた事: 以前の経験でMVVMで作られてたものをReduxへ置き換える、あるいはその逆をする経験をした際に、どの様に考えると結構わかり やすかったかという観点についても簡単ではありますがご紹介できると思います。 3. 最新のTCA(v1.7系)のドキュメントやサンプルに触れた際の所感: 最近あまりキャッチアップできずじまいのまま過ごしていました。iOS17からは「Observation Framework」の登場もあり、TCAに も変更があったので久しぶりに見てみると興味深い点もありましたので簡単に紹介できればと思います。
  3. Viewにおける状態変化と更新手段は共通点がある Actionを発行して副作用を伴うReducer処理で新たなStateを作成する流れは同様 1. ReduxでのView更新までの流れ: 2. TCAでのView更新までの流れ: Unidirectionalなデータの流れを作る方針はとても類似しているが副作用に関する考え方が特徴的に感じる。 View要素から実行された Actionを発行する Middleware(副作用)が

    処理前後で実行される 該当するAction合致時は 内部処理を利用して別の Actionを発行する Reducer処理内でState内 のPropertyを更新する Middleware(副作用)がな い場合は直接Reducerへ 全体のStateが更新され View要素を更新する View要素から実行された Actionを発行する Effect(副作用)が Reducer内で実行される Reducer内処理において Effectを利用して内部で 別のActionを発行する Reducer処理内でState内 のPropertyを更新する Effect(副作用)がない場 合は直接Reducerへ 全体のStateが更新され View要素を更新する
  4. case .fetchRecentNews: // (省略)Loading状態を表現するためにStateの内容を更新する // (処理例)API経由で最新情報データを取得する処理 return .run { send

    in await send( .fetchRecentNewsResponse( Result { try await self.newsRepository.fetchRecent() } ) ) } ReduxのMiddlewareとTCAのEffectのイメージを整理 APIリクエスト結果で成功・失敗のAction発行からStateの更新処理における事例 case let .fetchRecentNewsResponse(.success(response)): // (省略)成功時の状態を表現するためにStateの内容を更新する return .none case .fetchRecentNewsResponse(.failure): // (省略)失敗時の状態を表現するためにStateの内容を更新する return .none 同じReducer内で .send(…) を実行してActionを発行  @Dependency経由で取得したRepositoryの処理を実行 func resentNewsMiddleware() -> Middleware<AppState> { return { state, action, dispatch in switch action { case let action as FetchRecentNewsAction: requestFetchRecentNews(action: action, dispatch: dispatch) default: break } } } Task { @MainActor in do { let response = try await NewsRepositoryFactory.create().fetchRecent() dispatch(SuccessFetchRecentNewsResponseAction(response)) } catch APIError.error(let message) { dispatch(FailureFetchRecentNewsResponseAction()) } } } 1. TCAのEffectを利用した処理: 2. ReduxのMiddlewareを利用した処理: Middleware関数内でRepositoryをインスタンス化して処理を実行 関数内で dispatch() を実行してActionを発行 private func requestFetchRecentNews(action: FetchRecentNewsAction, dispatch: @escaping Dispatcher)
  5. 自作したRedux処理からTCAだったらなと感じた点 アーキテクチャ部分の提供だけではなくかゆい所に手が届く様な配慮がある 1. Reduxを自作した際のつらみ?の部分: 2. もしTCAだと嬉しく感じる部分: 両方を試した事で「この部分を提供してくれるのは本当にありがたい」と感じる事は多かった。 Storeを分割できる機構を持っている .scope(state: \•••,

    action: \◆◆◆) で制限する Stateの親子関係を意識したState構造 特に異なる画面にStoreを渡したい様な場合には、実装処理や構造を利 用してうまく範囲を制限したりする等の配慮が必要 @EnvironmentObjectを利用したDI機構 APIリクエストやデータ永続化処理をそれぞれMiddlewareに分割して Storeに定義するので、ファイルが多くなると結構大変な印象 Swift Concurrencyのサポート対応 async / awaitでの処理機構・@Sendableへのケアが自前で必要 Viewにおいて直接Store全体を観察する形にすると、ある状態が変化し た場合、SwiftUIはすべてのUI更新を要求するため全体を再計算する DI機構・APIClient等をはじめとしたサポートが充実 @Dependency / liveValue / point-free製のOSS等 PropertyWrapperの形で提供されている便利な機構や自前で作成すると 大変そうな部分や処理等を提供してくれている
  6. 元々MVVMで作成された画面をTCAに置き換えるアイデア どちらも共に良いと思うが(今回はあえて)置き換える事を考えてみます ① 初期状態 : メールマガジン登録をするための画面: メールを送信する メールマガジン登録 📩 Mail:

    [email protected] メールを送信する メールマガジン登録 📩 Mail: abcdefc123456 Invalid. メールマガジン登録処理に関する仕様 この時はメールを送信するためのボタンは非活性状態となる ② 入力中の状態 : 形式が正しくない場合のボタンは非活性状態となる テキストフィールドの左下にエラーメッセージが表示される ③ 入力完了の状態 : 形式が正しい場合のボタンは活性状態となる テキストフィールドの左下にエラーメッセージが表示されない ※ 目のボタンを押下すると入力内容がクリアされる 👀 👀
  7. SwiftUIでのMVVM構成に関するポイントをまずは確認 MVVM構成を考える際はViewModel内部実装とSwiftUIのStateに関する事 1. 基本方針と構成のおさらい : View Components ViewModel UseCase・Repository Infrastructure

    ※ AndroidではStateもViewと分離 Button活性・非活性状態を管理する: ※基本的にはasync/awaitでLogicを作成して、Viewとの連結時に必要に合わせてCombineで補う方針 2. SwiftUIのView要素の特徴を踏まえてポイントを整理する : ① SwiftUIのVIew要素ではStateが含まれる形にそもそもなっている ② ViewModelはObservableObjectを継承し、View要素では @ObservedObject / @StateObject で連結する @Published private(set) var sendButtonDisabled: Bool = true @Published var inputEmail: String = "" 入力用TextFieldと連結する: ※ Binding<String>で渡す必要があるため Input Output ViewModel内に定義したメソッド: viewModel.doSomething() ViewModel内の@Publishedで定義したProperty 双方向Bindingの様な形のイメージ
  8. MVVMでの処理をTCAに置き換える際のイメージ(1) Inputは定義したメソッドの実行・Outputは@Publishedで定義した変数 ① 入力するTextField要素とBindingするProperty : メールを送信する メールマガジン登録 📩 Mail: [email protected]

    ViewModel処理における組み立て方のヒント ※ didSet {…} を利用して関連する値を更新したり、Validation処理を実行させる様にするのがポイント メールマガジン登録画面をMVVMで実装する場合のViewModelにおける見通し: 👀 @Published var inputEmail: String = "" ② Button要素の活性状態をHandlingするProperty : ※ Input用のメソッドやTextFieldの入力状態によってこの値が変化する様に調整する @Published private(set) var sendButtonDisabled: Bool = true 双方向Binding前提の処理 ③ 送信要素を押下した時の処理実行メソッド : func postInputEmail() { … 入力データをPOSTで送信する … } Output Input ※ APIリクエストやデータ永続化の様なBusiness Logic(Domain Logic)の処理を実行する
  9. MVVMでの処理をTCAに置き換える際のイメージ(2) ViewModelに定義したInput・Outputをヒントにして置き換えていくと良さそう ① @Published で定義したOutput用のPropertyがStateのヒントになり得る : メールを送信する メールマガジン登録 📩 Mail:

    [email protected] この画面で利用すつStateを定義する 入力TextFieldと連動する & Button状態のHandling用のProperty → StateのPropertyになる? メールマガジン登録画面をTCAで実装する場合のポイントになりそうな部分: 👀 Reducer処理とAction名を定義する State ② Action名はInput用のメソッド名がヒントになり得る : Recucerに定義している各種case名(すなわちAction)は @Published を更新するためのメソッ ド名を命名や内容を参考に置き換えてみる Dependencyを利用する事でBusiness Logic(Domain Logic)の定義を利用可能にする Reducer Action @Dependency(\.mailMagazineRepository.postInputEmail) var postInputEmail 入力データをPOSTで送信する
  10. まとめ TCAの変化スピードは早く激しいが興味深い点も多く楽しいと感じている 1. Reduxの流れを汲む点と副作用に対する考え方を知ると理解がし易いと思います: Unidirectional(単方向)なデータの流れを実現する基本的な流れについては、共通点も多い印象がありますが、その一方で副 作用的な処理をする機構については考え方が大きく異なる点を知る事が、最初の理解を深める足掛かりになると思います。 2. MVCやMVVMでの実装からどの様に整理と分解をするかを考えてみるのも1つの手かと思います: 構成だけを一見すると全く別物の様に思いますが、InputとOutputのPropertyやMethodを整理していくと、TCAで必要な要素であ る「Action

    / Reducer / State」へ置き換えて考える事の手助けになる場合も多いと思います。 最近業務でも少し触れる機会があったので、簡単ではありますが所感をまとめました。 3. TCAは全体的にAppleが提供しているAPIを使うための良い形を模索している様に見えました: スピーディーかつ大きな変更やバージョンアップも頻繁にあるものの、まさしく「かゆい所に手が届く」対処が施されている点 やより便利かつ直感的な形を実現できる様に、最新iOSの変更も積極的に取り組んでいる点は本当に興味深く感じております。