[Swift]protocol extensionの上手な使い方

こんにちは。 InstantiateというSwiftライブラリの内部実装を調査した内容をメモがわりに置いておきます。

Instantiateについて

Instantiate(GitHub)はViewControllerのサブクラスなどが依存するクラスをDI(Dependency Injection)したい時などに、よしなにしてくれる便利なprotocolが実装されています。
 
例えば、ViewControllerがViewModelを依存させたい!という場面があることとします。
Swift
上記のようにViewControllerを宣言してからViewModelをなんらかのメソッド(今回は`inject`メソッド)で呼び出しても良いですが、これではメソッドの呼び忘れなどのヒューマンエラーが起こりうるのです。
そこで、Instantiateを利用すると下記のように初期化時に保証できるようになります。
Swift
なるほど、便利ですね!
かなり簡潔に見えますがこれはどうやって実装されているのでしょうか?

Injectableプロトコル

まず、依存させたいクラス(上記ではViewModel)をどうやってViewControllerに設定するかですが、下記のように特定のprotocolに準拠させるだけです。
StoryboardでViewContorllerのUIを作っていればStoryboardInstantiatableで、NibであればNibInstantiatableになります。
また、InstantiateStandardは同じクラス名のファイルを自動的に検出していい感じにしてくれるのでimportすると良いです。
Swift
このStoryboardInstantiatableinjectableを継承しています。
また、ViewController初期化時に、内部でStoryboardからViewControllerを生成し、injectメソッドを呼んでくれています。
そして、このinjectメソッドはprotocol extensionとしてあらかじめ実装されています。
(わかりやすさのために簡略化しています)
Injectable.swift
Swift
Storyboard+UIViewController.swift
Swift
もともとのInjectableには`Dependency = Void`とあり、すでに型が指定されています。
そのまま使うとするならViewModelなどの型を指定できず、Voidが入ってしまうのでは?という疑問が出てきます。
Swift

protocolを作ってみる

Playgroundでも用意して実際に試しながら読むとさらに理解が深まるかと思います。
このprotocolの仕様を追うのにまず理解すべきはassociatedtypeについてです。
まずは下記のようにprotocolでassociatedtypeとしてDependencyを用いるように定義してみます。(\*1)
また、各VCがtypealiasとして定義し具体型として実装できます。
Swift
では、ここでprotocol自身で定義しつつ特定の型を指定してみましょう。(\*2)
protocolを継承したclass(ここではVoidVC)では省略可能であることがわかります。
一方、StringVCでは Dependency = Stringとして上書きできます。
Swift
上記ではどちらのVCからcallメソッドを呼んでも、( \*1 )とおなじ結果が得られます。 ではcallメソッドを実装してみましょう。
Swift
上記を省略した形で下記のように書くこともできます。
Swift
callメソッドの引数に注目するとDependencyStringと違って見えます。
もうお気づきの方がいるかもしれませんが、callメソッドのdependencyに具体型を定義することで、コンパイラがtypealiase Dependency = Stringとして勝手に解釈してコンパイルが通るようになります。
これがViewModelなどのように様々な型を依存させることができる仕組みです

まとめ

今回はInstantiateというライブラリを題材に、内部実装をみていきました。
 
  • associatedtypetypealiaseの使い方
  • protocol extensionについて
上記について少しでも理解の一助になれれば幸いです。
Swiftの言語仕様をうまく使っていてとても参考になりますね。👏
こうやってOSSの実装方法を調査してみるととても勉強になるので継続的にやっていけたらと思います。
最後までお読みいただきありがとうございました。