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. Overviewing TCA v1.7 & Back and forth with MVVM
    potatotips #86
    2024/02/07
    Fumiya Sakai

    View full-size slide

  2. 自己紹介
    ・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

    View full-size slide

  3. iOSのUI実装本を執筆しています!
    書籍に掲載したサンプルのバージョンアップや続編等に現在着手中です。
    少しの工夫で実現できるTIPS集やライブラリ表現の活用集をはじめとした、iOSア
    プリ開発の中でも特にUI実装やUIKitを利用した画面の中で特徴を与える様な表現
    という題材に焦点を当てた書籍となっております。
    現在は電子書籍版のみとなります。 こちらは全て¥1,000となっております。
    https://just1factory.booth.pm/
    概要:
    https://book-tech.com/
    価格:
    📖 Booth
    📖 Book Tech

    View full-size slide

  4. UI実装であると嬉しいレシピブックの最新情報
    UI実装であると嬉しいレシピブックVol.3として昨年10月に商業化しました!
    Still

    WIP
    これまでの同人誌として頒布したものに加えて、Vol.1及びVol.2に頒布したものの
    中で書籍に載せきれなかったものや表現や動きが特徴的でユーザーにもほんの少し
    遊び心を与える様なUI実装を紹介したものをVol.3としています。
    概要:
    これからの構想:
    こちらで購入可能です:
    Amazon / Google Play / Apple Books / KINOKUNIYA / Rakuten BOOKS etc..
    🏊 iOS: SwiftUIを利用したUI実装や動画関連の実装
    🏊 Android: Jetpack Composeの基本やその他気になるUI表現の考察

    View full-size slide

  5. 今回のスライドにつきまして
    個人的にTCAに関連するトピックスや実際に軽く触れてみた際の所感を伝えたい
    僕自身もTCAの熟練度についてはそれ程高くはないのでお手柔らかにお願いします。
    1. 以前にも業務や個人活動の中でReduxを利用した経験があり以前からも関心はあった:
    Reduxの機構を利用したアーキテクチャを利用した開発経験を通じて、特に複雑かつ構成要素が複雑な入力を伴う画面等において
    は力を発揮する場面もありました。Reduxの考え方を採用しているTCAの動向は個人的にも気になっています。
    2. MVVM ⇔ TCA or Reduxの処理を置き換える経験をした際に感じてた事:
    以前の経験でMVVMで作られてたものをReduxへ置き換える、あるいはその逆をする経験をした際に、どの様に考えると結構わかり
    やすかったかという観点についても簡単ではありますがご紹介できると思います。
    3. 最新のTCA(v1.7系)のドキュメントやサンプルに触れた際の所感:
    最近あまりキャッチアップできずじまいのまま過ごしていました。iOS17からは「Observation Framework」の登場もあり、TCAに
    も変更があったので久しぶりに見てみると興味深い点もありましたので簡単に紹介できればと思います。

    View full-size slide

  6. まずはSwiftUIとReduxを利用したサンプル実装例(1)
    SwiftUI+Reduxを利用したUI実装サンプルにおけるポイント解説: https://zenn.dev/fumiyasac/articles/01f1bc86bf8c40
    以前に自作したサンプル実装例における概略図解と副作用に関する処理図解

    View full-size slide

  7. まずはSwiftUIとReduxを利用したサンプル実装例(2)
    GitHub Example: https://github.com/fumiyasac/SwiftUIAndReduxExample
    簡単なasync/awaitを利用したAPI処理やお気に入り処理を盛り込んでいます

    View full-size slide

  8. 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要素を更新する

    View full-size slide

  9. 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 {

    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)

    View full-size slide

  10. 自作した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の形で提供されている便利な機構や自前で作成すると
    大変そうな部分や処理等を提供してくれている

    View full-size slide

  11. 元々MVVMで作成された画面をTCAに置き換えるアイデア
    どちらも共に良いと思うが(今回はあえて)置き換える事を考えてみます
    ① 初期状態 :
    メールマガジン登録をするための画面:
    メールを送信する
    メールマガジン登録
    📩 Mail:
    [email protected]
    メールを送信する
    メールマガジン登録
    📩 Mail:
    abcdefc123456
    Invalid.
    メールマガジン登録処理に関する仕様
    この時はメールを送信するためのボタンは非活性状態となる
    ② 入力中の状態 :
    形式が正しくない場合のボタンは非活性状態となる

    テキストフィールドの左下にエラーメッセージが表示される
    ③ 入力完了の状態 :
    形式が正しい場合のボタンは活性状態となる

    テキストフィールドの左下にエラーメッセージが表示されない
    ※ 目のボタンを押下すると入力内容がクリアされる
    👀
    👀

    View full-size slide

  12. 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で渡す必要があるため
    Input Output
    ViewModel内に定義したメソッド: viewModel.doSomething() ViewModel内の@Publishedで定義したProperty
    双方向Bindingの様な形のイメージ

    View full-size slide

  13. 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)の処理を実行する

    View full-size slide

  14. 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で送信する

    View full-size slide

  15. Observationの登場とTCAの最新動向をおさらいする(1)
    Observationへの対応や既存の書き方が変化する等以外と変更があると感じた
    v1.7以降ではObservationをベースにした形でViewStoreが不要になったの変更点の一番HotなTopic :
    ① GitHubのREADMEでの実装事例も更新されている :
    Observationの変更がメインではあるが、PropertyWrapper関連の変更や記載の変更等も含まれていました。
    ② 変更点の一覧は「Migrating to 1.7」を参照 :
    Observation利用Versionのコード差分が紹介されています。 README記載のもの以外での変更点も色々紹介されています。

    View full-size slide

  16. Observationの登場とTCAの最新動向をおさらいする(2)
    データ永続化を利用しないTODOリストのExampleですが基本が詰まっている
    Reducerに関する変更点やView要素との連携方法が一通り学べそう :

    View full-size slide

  17. Observationの登場とTCAの最新動向をおさらいする(3)
    目次を眺めてみると「Replacing … 」の項目が結構多くある印象でした
    よりコードとして自然な書き方を目指している様にも見受けられます :
    「Migrating to 1.7 より」項目を抜粋してみました:

    View full-size slide

  18. まとめ
    TCAの変化スピードは早く激しいが興味深い点も多く楽しいと感じている
    1. Reduxの流れを汲む点と副作用に対する考え方を知ると理解がし易いと思います:
    Unidirectional(単方向)なデータの流れを実現する基本的な流れについては、共通点も多い印象がありますが、その一方で副
    作用的な処理をする機構については考え方が大きく異なる点を知る事が、最初の理解を深める足掛かりになると思います。
    2. MVCやMVVMでの実装からどの様に整理と分解をするかを考えてみるのも1つの手かと思います:
    構成だけを一見すると全く別物の様に思いますが、InputとOutputのPropertyやMethodを整理していくと、TCAで必要な要素であ
    る「Action / Reducer / State」へ置き換えて考える事の手助けになる場合も多いと思います。
    最近業務でも少し触れる機会があったので、簡単ではありますが所感をまとめました。
    3. TCAは全体的にAppleが提供しているAPIを使うための良い形を模索している様に見えました:
    スピーディーかつ大きな変更やバージョンアップも頻繁にあるものの、まさしく「かゆい所に手が届く」対処が施されている点
    やより便利かつ直感的な形を実現できる様に、最新iOSの変更も積極的に取り組んでいる点は本当に興味深く感じております。

    View full-size slide

  19. Thank you for listening !
    今回は本当にエッセンスだけの紹介になりましたが、TCAのキャッチアップはとても楽しいものです。

    改めてUI実装サンプル実装&記事化できる様に進めていければと思います!

    View full-size slide