The Composable Architeecture
TCAと略される
ライブラリとして提供されており,Fluxっぽいアーキテクチャを難しいことがわからなくてもかんたんに構築できる.
ケースタディとしていくつかのサンプルが公開されている
特徴
- State management
- Composition
- Side effects
- Testing
- Ergonomics
ケーススタディ
- EffectsBasic
- プラスとマイナスボタンがあり,マイナスがタップされると1秒後に自動的にプラスされ,戻される
- 更に,Number Factボタンが有り,それを押すと今のカウントでWebAPIを呼び出す
- Reducer
- マイナスボタンが呼び出されると,delayが入ったEffectを作って返している
- Number Factボタンが呼び出されると,現在のstateに対してAPIを呼び出し,返り値をEffectにmapして流している
- 成功するとSuccess,失敗するとFailureが流れるようになっている
- Stateを変更できるのはReducerのみ
- Viewとロジックをひとまとめにして,それを部品として組み合わせられる
- 大きなReducerを並べなくて良い
- 副作用はEffect型にして,次のエフェクトも繋げられる
中で何をしているのか
- WithViewStore
- 内部としてはSwiftUIのView
- 外からStoreを入れて,中でViewStoreにしている
- ViewStoreとは,ObservedObject→SwiftUIと相性が良い
- ViewStore
- ObservedObjectのwillChange時にStateを送れるようになっている
- Effect
- CombineのPublisherに準拠している
- TCA内部でSubscriber監視できる
- オペレーターで処理をチェインできる
- StoreはViewStoreからActionを呼び出すと,現在のActionのキューに保存
- Actionを順次呼び出し
- Effectを順次実行
テストコード
どういうふうにテストを書くかが明文化されている
ActionによってReducerがStateを期待通り変更できるか?が知りたい →ニセのEffectを入れることが可能.テスト用のScheduerもある.
Effectに対するテストを書きたいときは,単体テストを書くと良い
まずTestStoreを作る
Environmentにテスト用のEffectを渡す
store.assert
で期待するStateと比較できるdo
メソッドでTestScheduerを一つすすめることで,副作用が適用されるreceive
メソッドでTCAはReducer用のテストコードを提供してくれる
まとめ
- 良いところ
- ReducerからしかStateを扱えない
- Reducerを分割できるため,大きなReducerを作らなくて良い
- SwiftUIでも使いやすい(UIKitで扱う方法も書いてある)
- Combineをあんまり理解していなくても,実行と監視の恩恵がある
- テストスケジューラとテスト方法を提供してくれる
- 難しいところ
- ある程度Combineを理解していないと何が起こっているのかわからない
- CombineがOSSじゃないので,Combine自体のコードが読めない
- 基本的にはアプリ設計はTCAでやりきらないといけない(気がする)
- 他の画面はMVVMでやるとかは難しい
- ライブラリに依存するという覚悟はいる
Qiitaにも記事を挙げられている
- SwiftUI時代のFunctional iOS Architeectureというトークもある
Ask-the-speaker
- Reducerはアプリ全体で一つではなく,画面ごと?
- 厳密に言うと,画面がなくても作れるので,画面ごとではない
- 複数Reducerがあって良い
- 画面に紐付いていなくて良い
- Core/DateとかでDataBaseの監視用のものを作ることがあるが,それをStore(Reducer含む)で監視することも可能
- WithViewStoreとかは画面ごとに存在する
- 厳密に言うと,画面がなくても作れるので,画面ごとではない
- Effectは細かいものをたくさん作る?
- そう.で,サブスクライブしてつないでいく.
- API呼び出しとかは全部Effectで表現する?
- そう.Completionとかも表現できるよ
- Effectが副作用をまとめて管理している?
- そう
- Storeについてアクションを発行できない
- ユニバーサルリンクのイベントとかどうするんだろう
- SwiftUIだとiOS 14だとエントリポイントのViewがユニバーサルリンクのイベントをハンドリングしてくれそう?
- Appのデータ持っているのはStoreで,それをwithViewStoreでViewStoreに変換する
- スコープ作る
- そのwithViewStoreに対してアクションを呼び出せばOK
スモールビジネスを支えるfreeeのモバイル技術
全ては見てはいないので軽くメモ
- iOSアプリ開発ではよくUIモジュールをライブラリ化してそれを使うようにしているのを見かける
- おそらく,テーマがないので部品をCustom Viewみたいな感じで作っておいてそれを使う感じと推測
- 「会計ドメイン」はロジックがすべてのプラットフォームで同じなので,Kotlin MPPを使用してロジック部分を共通化している
- iOS向けにはframeworkがGradleにより生成される
- ライブラリ呼び出しは非常に自然
- Kotlin MPPのframework生成について詳しく
- frameworkの中には,コンパイルされたバイナリファイルとヘッダファイルが含まれている
- バイナリはx86,arm_64などで別ディレクトリに吐き出される
- ヘッダファイルの型などはObjective-CベースなのでSwiftから呼び出すときには成約が出てしまう
- Kotlin MPPがKotlinとの型の差分を埋めるために定義する型も存在する
- 例えばLongなど
- NullableのLongはSwiftではSampleLongという型がKotlin MPPにより生成されて使用される
- SampleLongはSampleNumberの実装で,SampleNumberはNSNumberの実装になっている
- expect/actualで各プラットフォームに処理を委譲できる
- でもこれやりすぎたら共通化できてなくね?(?)
- できればCommonに全部かければ良いと思う・・・(個人的な感想)
Webとネイティブアプリの付き合い方を改めて考える
技術 | Web | PWA | ネイティブ | AppClip |
---|---|---|---|---|
Webとネイティブアプリの付き合い方
- ネイティブアプリと同じ体験をWeb(PWS含む)でも提供できるか?
- 難しい.
- WebAPIを利用することで機能としては同等の機能を提供できる
- ただし,画面遷移やまだ使えない技術が多いため,難しい
- どう言うときにWebを使用したら良い?
- Webのメリットを享受したいと聞か,ネイティブアプリのリスクを回避したいとき
- 細かいUXの完成度より,まず広めることを重視するとき
- PUSH通知とかがいらないとき
- ネイティブアプリのデメリット
- 専門性が高い
- Android版も作らないといけない
- Webのメリット
- 検索からの流入が見込める
- 両OSに対応できる
- ただし,それぞれのOSでユーザがなれたUIを提供するには独自実装が必要
- 審査不要
- 両方を提供するメリットは?
- いろいろな戦略を取ることが可能になる
- ネイティブアプリとWebではそもそも流入経路が違う
- iOSネイティブ+PWAを組み合わせると,マルチデバイスを低コストで実現可能
- WebとApp Clipsの療法を提供するメリットは?
- App Clipsはネイティブアプリの呼び水として
- リアル接点を持たないWebサービスから見ると,App Clipsを提供するメリットは限定的
まとめ
- ブラウザの進化により,「ネイティブじゃないと機能を提供できない」という状況は減ってきている
- メリデメを考えて比較検討しよう
Ask-the-speaker
- Webでネイティブと同じ体験を提供するのが難しい部分について,もう少し詳しく
- PUSH通知
- アニメーション
- なめらかな画面遷移をOSのバックアップを受けながら実装できる
- Webのものをホーム画面に追加というアクションそのものにユーザが不慣れ
- 開発環境はどう?
- Webでも最近だとTypeScriptなどがあるので大規模開発もできそう
- 一応ブラウザの機能も規格化されているため,勝手に誰かが開発してぶっ壊れることも少ないのでは
- 側ネイティブがPWAとかに比べてメリットになるのはどういうとき?
- Webでメインを作りながら,アプリも提供できる
- ただし,Appleとしては側ネイティブはやめてくれ,と言っている
- Webからネイティブを呼び出したくなったときなど,ネイティブの知識が必要になることがある
- Webのほうが作りやすいならWebViewで,というのもありそう
- オフライン対応はどうか?
- オフライン対応を半強制的に考えないといけないのはネイティブ
- Webならアクセスできなければ真っ白になるか,ブラウザのエラーが出るのでそれで良しとすることも可能
- PWAでもキャッシュする部分を指定して側の部分だけをキャッシュしておいて,オフラインでも使えるようにすることは可能
SwiftのWebAssembly対応の軌跡
WebAssemblyとは,スタックベースの仮想マシン上で動く命令セットの一部
ポータブルに動くのがウリ
通常,クロスプラットフォームに対応するためにはそれぞれの環境に対応するバイナリを配布することになる
→WebAssemblyなら一つのバイナリをブラウザなど様々な環境で動かすことが可能
Swiftでは正式サポートはされていない
SwiftがWebAsmに対応すると嬉しいこと
- SwiftでWebフロントがかける
- Server Side SwiftやiOSとコード共有可能
- Switが対応していない環境でもWasmランタイムさえあれば動く
SwiftWasm Projet
- SwiftのWebAsm対応をするためのプロジェクト
Demo
- ブラウザ上でSwiftを実行することが可能!(すごい)
- コンパイルはサーバでやるが,実行はブラウザ上でやるので安全
- GUIも作れる(DOM)もいじれる
SwiftWasmプロジェクトの道のり
コンパイラのWebAsm対応
Paese→AST→Sama→SILGen→SIL(中間表現)→IRGen→LLVM IR→ASM
LLVM for WebAssembly
- Swift Wasmプロジェクト以前からWeb Assemblyをサポート
- SwiftのSILの一部の機能をWasmが対応していなかったため,Swiftが吐き出したLLVM IRはそのままだとWebAmsとして動かなかった
- →これを治すパッチを出した
ランタイムライブラリ&標準ライブラリのWasmサポート
- Swiftのランタイムを作るときにいくつかの特殊なテクニックが使われていた
- →これをWasmに導入した
エコシステム
- swiftwasm/JavaScriptKit
- Dom操作をSwiftから行える
- JavaScriptライクなオブジェクト操作APIを提供
- メモリ管理等の差を吸収
- swiftwasm/carton
- SwiftWasm製Webアプリのビルドツール
- TokamakUI/Tokamak
- SwiftUI互換のUIライブラリ
- DOM操作と静的HTML生成をサポートしている
現状の課題
- バイナリサイズ問題
- 他の言語と比べるとWasmにしたときにサイズが大きい
- SwiftはUnicodeデータベースや正規化のためにICUに依存している
- ICUだけで4MB以上ある
- Swiftの標準ライブラリは非常に大きい
- GoやRustは細かく別れていて使われないものはリンクされない
- 使われない機能もバイナリに含まれてしまう
- 一応標準ライブラリのサイズ最適化機能の実装プロジェクトがある
- モジュールをまたいで使われていないコードを削除することでサイズを削減するコードが書かれている
- SILレベルでSwiftのセマンティクスを最適化
- LLVMレベルより積極的な最適化が行える
- 最適化すれば60%ほどの削減を達成
- kateinoigakun/swift-lto-benchmark
まとめ
- 実験段階として利用可能
- 周辺ツールやライブラリの対応も進んでいる
- バイナリサイズは改善の余地あり
Ask-the-speaker
- Webブラウザ以外でも動くといっていたが,例は?
- 実験段階だが,k8sのランタイムとしてDockerが一般的だが,ランタイムとしてWasmが使われる可能性がある
- CDNのフィルタ
- Emboyの安全なプラグインとして使われている
- Swiftで書いているOSSのライブリをWasmに対応させようとしたら大変?
- ネットワークのURLSessionとかUIKitとかがまずそう
- UI部分に依存しないOSSなら良さそう
- FileIOとかはサポートしている
- Objective-Cが入っていると辛い?
- libdispatchがリンクできなかった(結論)
- SwiftWasmにContributionしたいと思ったときにどこから始めたら良い?
- Tokamakが一番とっかかりやすい
- Webブラウザ上でDebugしたいときはどんな感じ?
- ブレークポイントとかどう?
- ほぼできないChromiumとかの実装が必要になってくる
- ちょっとだけ入っているが,SwiftのWasmのバイナリサイズが大きいからか,デバッグ情報のパースがうまく行かなかったりする
- kateinoigakunさんがコンソールでデバッグできるやつを書いた
- 大学との両立とかは?
- サークルとかには行かずにインターネット上で友達とかを作って色々やっていた
- インターンで特定のタスクにアサインされていないのでそういう働き方ができているのかも?
- TokamakでCSS当てるのはどうする予定?
- まだ決まってはいない
- どの程度までCSSの存在を隠蔽するかなど,難しいところが多そう
- 隠蔽せずにCSSを生でかけたほうが幸せという意見アリ
- HTMLタグの名前のView部品を組み合わせられると嬉しいなぁ
SwiftUI時代のFunctional iOS Architecture
昔は関数型アーキテクチャはほぼ実現できなかった
SwiftUIとCombineで時代が来た
Harvest
発表者が作ったライブラリ?
- 基本的にはElm Architecture
- 副作用をキューで管理する
- FRPの考え方を活用している
- Optics
- Lens: 状態(通常はstruct直積型)に対する2つの操作
- Prism: アクション(通常はenum直和型)に対する2つの操作
- これがあることにより,状態やアクションのツリーの深い階層の一箇所を更新するときに便利?
- 各階層間のget/setペアのことをLensと呼ぶ
- これら2つを合成してdeepなget/setが可能
- アクションでは矢印が逆になる
- 子が発火すると戻って親まで伝搬し,そこからさらに子に戻ってくる.
- その差異,build/try getを使用する
- これも合成可能
- 各階層間のget/setペアのことをLensと呼ぶ
- 証明に米田の補題を使うらしい.かなり来てるぞ
- ChildStateからParentStateへのReducerはFunctorとみなせる
- →Reducerの型変換ができ,更にそれを合成可能!
- 状態とアクションを各コンポーネントごとに疎結合に管理できる
- アプリのStateとActionを巨大にせず,ツリー構造に分解
Composable Architecture
- point-freeチームによるElm Architecture風の実相
- Multi-Store方式を採用している
- 最上位ではなく,親と子コンポーネントそれぞれに対してStoreが紐付いている
- それぞれのStoreはコピーとしてView階層に持たせる.Single Source of Truthではない
- 重複レンダリング対策のため,removeDuplicatesが行われる
- swift-case-pathsを使ったPrism実装
- structのkeypathのenum版
- Swiftのリフレクションを使用して,embedからextractを自動生成→本来両方いるが,embedだけで良い
Bow Arch
- Bow: lightweight higher kinded polymorphism
- 高カインド多相を実現できる
- Comonadic UI
- コモナド=オブジェクト指向-可変参照
- SwiftUIのViewをコモナドとして捉えている
- モナドはreturnとjoin
- コモナドはextractとduplicate
- extractはオブジェクトからコンテキストを消費してDを出力
- duplicate: オブジェクトの取りうる未来を映し出す(?)
- Component=Storeコモナド
- 実際のComponentはイベントハンドラがついているが,Storeコモナドを維持している
- Stateモナドを使ってComponentを操作する
- →コモナドはモナドとPairをなし,モナドで操作する(らしい)
コモナドx副作用(IO)
EffectComponent<W, V>
はOOPにおける「オブジェクト」(その複雑さを「コモナド」と「副作用」に分解する)EffectComponent<W, V>
は他のコモナドWにも使えるのでは?- Mooreコモナド
- Elm ArchitectureやReduxで使われている
- 余自由コモナド(Cofree)
- ペアは自由モナド
- PureScript Halogen
まとめ
- Comonad → Architecture
- Store → React
- Moore → Elm
- Cofree → PureScript
コモナドはアーキテクチャを規定する
- SwiftUIの本質はコモなど
- コモナドの構造がUIアーキテクチャのパターンを決める
- 状態,リアクション,Reducerを各コンポーネントごとに分解し,Opticsを使ってまとめ上げる
Ask-the-speaker
- 参考書
- プログラマが書いた本を読んだら良い
- Haskellやりながらが良い
- YouTubeとかでプログラミングから見た圏論を解説している人の動画とかから見始めると良い
- 稲見さんのおすすめ
- TCAは関数合成を意図的に避けている?
- 副作用を子から親に伝搬するときは関数合成はしていない?
- ツリー構造は副作用がない
- 副作用が絡むとモナドが出てくるので合成がやりにくくなる
テストコードが増えるとバグは減るのだろうか?「0→60.3%」で見えた世界の話
テストは大切なので皆さんも積極的にテストをしましょう
- テストを実装すればバグが減ると思った
- QAチームからのバグ報告が多かった
- 当時はまともに動くテストコードほとんどなく既存のコードの正当性が疑わしかった
- これから実装するものに関してだけでもテストを実装すれば未来の人にとても良いのではないか
メリット
- 使用の確認・理解に大きく貢献する
- 仕様の確認が強制的になされる
- リリース後に不具合が発覚するかも
- テストの種類
- 動的テスト
- プログラムでテスト
- 静的テスト
- レビューとか
- 両方やると大きな効果を得る
- 動的テスト
- テストの種類
- 不具合の検証がしやすい
- 機能追加を繰り返すと度のタイミングでバグが発生したのか,よくわからなくなってくる
- テストが入っているとこの検証がやりやすくなる!
- レアケースの再現が容易になる
- 普通怒らないようなレアなケースについて検証が可能
- テストへの意識・モチベーションの上昇
- お互いに学び合う環境が生まれる
- 心理的な安全
- 自分が書いたコードに自身がありますか?
- →テストを書けば自信が持てる!
デメリット
- 学習コスト
- 勉強することがたくさんある
- 知識や経験なしに適切なテストを書くのは難しい
- メンテナンスコスト
- 気づいたらテストが動かなくなっている
- テストがない開発に慣れているので回収をする際にテストを直さない事がある
- 壊れたコードを治すのに時間がかかり,意味不明な感じになる
- テストコードの神格化
- テストコードは万能ではない
- テスト導入ですべての問題が解決するわけではない
- テスト導入の良い点が語られがちだが,悪い点もあることを理解する事が大事
- その効果が目で見て分かりづらい
- テスト実装が考慮されていないコードにテストを導入するのはほとんど不可能
- 開発工数が増えるのでそこを考慮する必要がある
組織にあったテストの導入
- テストを導入する理由
- 「課題」を解決するため
課題
- レガシーコードからの脱却
- エンジニア・デザイナ間のコミュニケーションコスト
- レイアウトの変更があるとデザイナに確認する必要がある
- スナップショットテストを導入
- エンジニアのみでリグレッションテストが可能
ポイント
- 現状の課題をよく分析し,最適なテスト手法を導入する
- 費用対効果を意識する.導入に対して対価がどれだけあるのか?を考える
まとめ
- テストコードが増えるとバグは減るのか?
- ▲
- テストコードにバグがないこと
- テスト手法が適切である
- テストケースが適切に考えられていること
- 本当にテストすべき箇所にテストが実装されていること
- という条件を満たしていないとバグは減らない
- テスト導入でどんな変化があったか?
- 保守性が生まれた
- 未来を見据えて本当に良い設計はなにか
- 開発が効率化
- デバッグ・機能開発でスピード感が生まれた!