[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
この
StoryboardInstantiatable
はinjectable
を継承しています。
また、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メソッドの引数に注目すると
Dependency
と String
と違って見えます。
もうお気づきの方がいるかもしれませんが、callメソッドのdependencyに具体型を定義することで、コンパイラが
typealiase Dependency = String
として勝手に解釈してコンパイルが通るようになります。
これがViewModelなどのように様々な型を依存させることができる仕組みです
まとめ
今回は
Instantiate
というライブラリを題材に、内部実装をみていきました。associatedtype
やtypealiase
の使い方
protocol extension
について
上記について少しでも理解の一助になれれば幸いです。
Swiftの言語仕様をうまく使っていてとても参考になりますね。👏
こうやってOSSの実装方法を調査してみるととても勉強になるので継続的にやっていけたらと思います。
最後までお読みいただきありがとうございました。