SwiftのDI方法につい て最近考えてた話

2K Views

December 01, 19

スライド概要

社内iOS LT #28で共有した、「SwiftのDI方法につい て最近考えてた話」の内容です。

profile-image

2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

SwiftのDI方法について最近考えてた話 iOS LT #28 Kazuhiro HAYASHI

2.

開発にDIコンテナを導入しているのですが、 動的な性質が辛いため脱却を検討中。 その際に調べてた内容をシェアします。

3.

Dependency Injectionとは ・“制御の反転”を行うためのパターン ・“呼び出し元”が“呼び出し先”に依存する状態を実現する ・具体的な実装への依存が外に出る https://martinfowler.com/articles/injection.html

4.

Dependency Injectionとは ・“制御の反転”を行うためのパターン ・“呼び出し元”が“呼び出し先”に依存する状態を実現する ・具体的な実装への依存が外に出る https://martinfowler.com/articles/injection.html 呼び出し元のMovieListerが

5.

Dependency Injectionとは ・“制御の反転”を行うためのパターン ・“呼び出し元”が“呼び出し先”に依存する状態を実現する ・具体的な実装への依存が外に出る https://martinfowler.com/articles/injection.html 呼び出し先のインターフェースを利用

6.

Dependency Injectionとは ・“制御の反転”を行うためのパターン ・“呼び出し元”が“呼び出し先”に依存する状態を実現する ・具体的な実装への依存が外に出る https://martinfowler.com/articles/injection.html 呼び出し先のインターフェースの中身をクラスとして実装

7.

Dependency Injectionとは ・“制御の反転”を行うためのパターン ・“呼び出し元”が“呼び出し先”に依存する状態を実現する ・具体的な実装への依存が外に出る https://martinfowler.com/articles/injection.html AssemblerクラスがMovieListerへ実装を注入する

8.

Dependency Injectionとは Application 上位レイヤ 呼ばれる 依存 Framework 下位レイヤ ・こうして「上位レイヤが下位へ依存する」が実現 ・ユニットテストやフレームワーク切り出しができる

9.

Dependency Injectionとは ・ライブラリを使う「動的」なやり方と、そうではない「静的」なやり方がある ・動的にやるにはいわゆるDIコンテナを導入する ・静的に行う場合はCake Patternなどの実装パターンがある

10.

DIコンテナ

11.

DIコンテナ ・依存関係の登録と解除を決まったルールで行うことができるようになる ・依存関係は全てコンテナによって登録・解決されるようになる

12.

Swinject Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements Inversion of Control (IoC) for resolving dependencies. In the pattern, Swinject helps your app split into loosely-coupled components, which can be developed, tested and maintained more easily. Swinject is powered by the Swift generic type system and first class functions to define dependencies of your app simply and fluently. https://github.com/Swinject/Swinject

13.

Swinject 例えばこんな構造体とプロトコルがあるとして protocol Animal {} struct Cat: Animal { let name: String } struct Owner { let pet: Animal } ・OwnerはAnimalに依存 ・CatはAnimalの実装でnameプロパティを持つ https://github.com/Swinject/Swinjectのreadmeを参考に一部改変

14.

Swinject Containerクラスのregisterメソッドで依存関係を記述していく let container = Container() container.register(Cat.self) { (_, name: String) in Cat(name: name) } container.register(Owner.self) { (r, name: String) in Owner(pet: r.resolve(Cat.self, argument: name)!) } ・nameを受け取ってCatにインジェクト ・nameを受け取って、それを使ってCatの依存解決をして、Ownerへ渡す https://github.com/Swinject/Swinjectのreadmeを参考に一部改変

15.

Swinject Containerクラスのresolveメソッドで依存関係が登録されているとおり解決する let vc = ViewController() vc.owner = container.resolve(Owner.self, argument: "たま") ・Ownerを依存解決して初期化 https://github.com/Swinject/Swinjectのreadmeを参考に一部改変

16.

Swinject ・登録・解決が簡単かつ決まったやり方でできて便利 ・Swiftでユニットテストかけるようになる

17.

Swinjectを使っていて生まれた課題 1.依存解決の正しさは実行時にしかわからない 2.ライブラリへロックインされてしまう

18.

依存解決の正しさは実行時にしかわからない StringではなくOptionalを渡す let vc = ViewController() let name: String? = nil vc.owner = container.resolve(Owner.self, argument: name) 例えば先程のようにOwnerをresolveする時にStringではなくString?を渡してもビルドは通り、エラーが出力される

19.

ライブラリへロックインされてしまう ・依存解決がライブラリへ集約され、アプリ全体でそれに依存するため抜け出せない ・Swiftやこのライブラリのバージョンアップへついていくのしんどい

20.

コンパイル時チェックができてもっと軽量な仕組みに移りたい…

21.

Cake PatternのSwift実装

22.

Cake PatternのSwift実装 ・Protocolを使って依存をコンポジションしていく ・Protocol ExtensionによってProtocolに定義された変数・メソッドを使って実装を記述する ・(Scala版Cake Patternとは若干構成が違う)

23.

Cake PatternのSwift実装 ・Protocolを使って依存をコンポジションしていく ・Protocol ExtensionによってProtocolに定義された変数・メソッドを使って実装を記述する ・(Scale版Cake Patternとは若干構成が違う) コンパイル時チェックができてライブラリも不要!

24.

Cake PatternのSwift実装 4つの役割を持つプロトコルやクラスを作る 1.依存対象のプロトコルとその実装コード 2.依存するというのを定義したプロトコル 3.依存をまとめた上位レイヤのプロトコル 4.依存解決に特化したクラス

25.

Cake PatternのSwift実装 4つの役割を持つプロトコルやクラスを作る 1.依存対象のプロトコルとその実装コード 2.依存するというのを定義したプロトコル 3.依存をまとめた上位レイヤのプロトコル 4.依存解決に特化したクラス 4.を上位のレイヤで利用し、そこで同じものを作っていき、更に上位のレイヤで…というのを繰り返す

26.

1.依存対象のプロトコルとその実装コード protocol Animal {} struct Cat: Animal {} protocol AnimalComponent { var animal: Animal { get } } protocol Gender {} struct Man: Gender {} protocol GenderComponent { var gender: Gender { get } }

27.

1.依存対象のプロトコルとその実装コード protocol Animal {} struct Cat: Animal {} protocol AnimalComponent { var animal: Animal { get } } protocol Gender {} struct Man: Gender {} protocol GenderComponent { var gender: Gender { get } } 依存対象のプロトコル

28.

1.依存対象のプロトコルとその実装コード protocol Animal {} struct Cat: Animal {} protocol AnimalComponent { var animal: Animal { get } } protocol Gender {} struct Man: Gender {} protocol GenderComponent { var gender: Gender { get } } その実装コード

29.

2.依存するというのを定義したプロトコル protocol Animal {} struct Cat: Animal {} protocol AnimalComponent { var animal: Animal { get } } protocol Gender {} struct Man: Gender {} protocol GenderComponent { var gender: Gender { get } }

30.

2.依存するというのを定義したプロトコル protocol Animal {} struct Cat: Animal {} protocol AnimalComponent { var animal: Animal { get } } protocol Gender {} struct Man: Gender {} protocol GenderComponent { var gender: Gender { get } } 「そのプロトコルをプロパティとして持っている」ということを定義したプロトコル

31.

3.依存をまとめた上位レイヤのプロトコル protocol Owner: AnimalComponent, GenderComponent { func exec() } extension Owner { func exec() { print("\(type(of: gender)) has \(type(of: animal))") } } class Taro: Owner { let animal: Animal = Cat() let gender: Gender = Man() } protocol OwnerComponent { var owner: Owner { get } }

32.

3.依存をまとめた上位レイヤのプロトコル protocol Owner: AnimalComponent, GenderComponent { func exec() } extension Owner { func exec() { print("\(type(of: gender)) has \(type(of: animal))") } } class Taro: Owner { let animal: Animal = Cat() let gender: Gender = Man() } protocol OwnerComponent { var owner: Owner { get } } 何に依存するかを定義した上位レイヤのプロトコル

33.

3.依存をまとめた上位レイヤのプロトコル protocol Owner: AnimalComponent, GenderComponent { func exec() } extension Owner { func exec() { print("\(type(of: gender)) has \(type(of: animal))") } } class Taro: Owner { let animal: Animal = Cat() let gender: Gender = Man() } protocol OwnerComponent { var owner: Owner { get } } その依存を使った処理の実装

34.

4.依存解決に特化したクラス protocol Owner: AnimalComponent, GenderComponent { func exec() } extension Owner { func exec() { print("\(type(of: gender)) has \(type(of: animal))") } } class Taro: Owner { let animal: Animal = Cat() let gender: Gender = Man() } protocol OwnerComponent { var owner: Owner { get } } 1.で作った実装の注入を記述したクラス

35.

4.依存解決に特化したクラス protocol Owner: AnimalComponent, GenderComponent { func exec() } extension Owner { func exec() { print("\(type(of: gender)) has \(type(of: animal))") } } class Taro: Owner { let animal: Animal = Cat() let gender: Gender = Man() } protocol OwnerComponent { var owner: Owner { get } } 更にそれに依存するということを定義したプロトコルを用意して上位レイヤで使う…

36.

詳細なコード https://goo.gl/5LvNhk

37.

所感 ・要するにProtocolのコンポジションで依存定義・登録をしていく(シンプル) ・Cake Patternもどきのみで実務の開発へ無理なく導入できるか自信は持てない ・プロトコル数が一気に増えて無駄な複雑さが発生しそう

38.

自分の中での結論 ・本当に必要なところからDI化していくというのが無難かも ・段階的なDIを行う手段としてCake Patternもどきを使っていくのが良さそう

39.

参考資料 ・Swinject ・https://github.com/Swinject/Swinject ・Dependency Injection in Practice ・https://speakerdeck.com/yoichitgy/dependency-injection-in-practice-ioscon ・Inversion of Control Containers and the Dependency Injection pattern ・https://martinfowler.com/articles/injection.html ・Minimal Cake Pattern in Swift ・https://www.slideshare.net/_yyu_/minimal-cake-pattern-in-swift ・Using protocol composition for dependency injection ・http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/

40.

次回予告 ・「実装して学ぶリアクティブプログラミング」というタイトルで発表したいと思います