はじめに
Swift5.7からSelfまたはassociated typeを持ったprotocolが型として定義できるようになりましたね!
これによって今まで型消去のテクニックを使って実装していた部分がよりシンプルに書けるようになりました。
そもそも型消去って何よ
associated typeを持ったprotocolが型として定義できないとは
Swift5.7未満ではassociated typeを持ったprotocolを型として定義できませんでした。
どう言うことか実際に処理として表現してみます。
protocol Repository {
associatedtype Entity
func fetch() -> Entity
}
struct SampleEntity {
let text: String
}
class SampleRepository: Repository {
typealias Entity = SampleEntity
func fetch() -> Entity {
return SampleEntity(text: "")
}
}
上記ではRepositoryがassociated typeを持ったprotocolとして定義されています。
では、実際にRepositoryを型として定義しようとするとどうなるか見てみましょう。
// Repositoryはassociatedtypeを持っていて型として定義できないためエラーが出る
var repository: Repository = SampleRepository()
上記のようにRepositoryを変数の型に指定しようとするとエラーが出てしまいます。
このような場合、型消去のテクニックを使うことでエラーを回避しつつ、上記でやりたかったことを実現することが出来ます。
型消去テクニックを使って解決してみる
では、上記のコードを型消去のテクニックを使って実装してみましょう。
struct AnyRepository<Entity>: Repository {
typealias Entity = Entity
private let _fetch: (() -> Entity)
init<T: Repository>(_ repository: T) where T.Entity == Entity {
_fetch = repository.fetch
}
func fetch() -> Entity {
return _fetch()
}
}
var repository: AnyRepository<SampleEntity> = AnyRepository(SampleRepository())
repository.fetch()
Repository protocolを直接型に定義しないで、AnyRepositoryというものを噛ませているのがわかると思います。
AnyRepositoryはイニシャライザでRepositoryに準拠したクラスを受け取り、そのクラスが持つRepositoryに準拠している関数をAnyRepositoryで変数に保持しておきます。
そして、AnyRepositoryのfetch関数等が呼ばれた際に保持していおいた関数を呼び出すことで、イニシャライザで代入したクラスの処理を実施出来ます。
AnyRepositoryはprotocolではないため、型としても問題なく定義できます。
RepositoryをAnyRepositoryに変換してしまうことで、元のRepositoryを型として定義することなく使うことが出来ます。
型消去を使わない方法
ここまで読んでいたいだら薄々勘づいているかと思うのですが、型消去をするためにAnyRepository等といったボイラープレートコードを毎回用意するの面倒じゃないですか?
面倒だと思った方でかつSwift5.7以上を以上を使用している方がいらっしゃいましたらご安心ください、Swift5.7以降では型消去のテクニックを使わずに実装できます。
ようやくここから本題です。
どうやって型消去を使わずに実装するの?
至って簡単です、下記のように定義しましょう。
var repository: any Repository = SampleRepository()
これで終わりですか?
はい、終わりです。
associated typeを持ったprotocolを型として定義する際に、型の前にanyを付けてあげるだけです。
AnyRepository等を作成する必要もありません。
本題より型消去の話の方が長い。
終わりに
型消去を現時点で使っている箇所は上記でリファクタリングすることでボイラープレートを削減でき、見通しも良くなるので検討の余地ありかなと思います。