前提あるいは Array 版
Swift でシンプルな Protocol を用意して...
protocol ModelType: Hashable {}
protocol Repository {
associatedtype Model: ModelType
func save(_ models: [Model])
}
これらのプロトコルに適合するモデルとリポジトリーを作ってみる。
// MARK: - モデル
struct Book: ModelType {
let title: String
}
// MARK: - リポジトリー
extension Repository {
func save(_ models: [Model]) {
models.forEach { model in
print(model)
}
}
}
struct BookRepository: Repository {
typealias Model = Book
}
今回は Book モデルと BookRepository を作ってみた。Repository Protocol の save
メソッドは Protocol Extension で実装した。中身はダミー。ちゃんとやるなら、Dictionary を使ったメモリー・キャッシュか Realm 等を使ったディスク・キャッシュの実装を書くことになると思う。
これらのメソッドの使い方は次の通り:
let array = [ Book(title: "book 1"), Book(title: "book 2"), ]
let repository = BookRepository()
repository.save(array)
Set でも使いたい
さて、時には Array ではなく Set を使いたいケースがある。その場合の使い方は次の通り:
let set: Set = [ Book(title: "book 3"), Book(title: "book 4"), ]
let repository = BookRepository()
repository.save(Array(set))
Repository の save メソッドは Array しか受け付けないので、Set から Array に変換する手間がかかる。
この変換をなくしたい。Array とか Set とか気にせず使いたい。そんなことは可能だろうか?
Set 用のメソッドを用意する
Swift は型を厳密に区別するので、Set 用のメソッドを用意してみる。
protocol Repository {
associatedtype Model: ModelType
func save(_ models: [Model])
func save(_ models: Set<Model>)
}
extension Repository {
func save(_ models: [Model]) {
models.forEach { model in
print(model)
}
}
func save(_ models: Set<Model>) {
models.forEach { model in
print(model)
}
}
}
こうすると、さっきのコードは Array への変換が不要になる:
let set: Set = [ Book(title: "book 3"), Book(title: "book 4"), ]
let repository = BookRepository()
repository.save(set)
目的達成。ただ、力業で凌いだきらいが強い。もう少しスマートにできないものか?
Sequence を使いたい
Protocol Extension のコードを見てみると、中のコードは全く同じ。forEach
を使っている。forEach
メソッドは Sequence Protocol にある。そして Sequence Protocol は Array にも Set にも適合されている。
すると、Array や Set のメソッドを用意しなくても、Sequence Protocol に対して save メソッドを用意すれば良さそう。そう、こんな感じに...
protocol Repository {
associatedtype Model: ModelType
func save(_ models: Sequence<Model>)
}
残念ながら上のコードは動かない。やりたいことは分かったけど、実現方法が分からない。
というわけで、本記事のタイトルに書いたスタート地点に立った。
Set.union を参考に
そういえば、Set の union
メソッドは引数に Set でも Array でも渡すことができた。あの実装はどうなっているんだろう?
コード・ジャンプを使って実装を見てみよう:
extension Set : SetAlgebra where Element : Hashable {
public func union<S>(_ other: S) -> Set<Set<Element>.Element>
where Element == S.Element, S : Sequence
なるほど、Sequence.Element
で型を指定している。この方法を真似ればよさそう:
protocol Repository {
associatedtype Model: ModelType
func save<S>(_ models: S) where S: Sequence, S.Element == Model
}
extension Repository {
func save<S>(_ models: S) where S: Sequence, S.Element == Model {
models.forEach { model in
print(model)
}
}
}
これでコードを書いてみると...
let array = [ Book(title: "book 1"), Book(title: "book 2"), ]
let set: Set = [ Book(title: "book 3"), Book(title: "book 4"), ]
let repository = BookRepository()
repository.save(array)
repository.save(set)
Array でも Set でも問題なく使うことができた。
あとがき
Sequence Protocol を意識することで、Array でも Set でも使えるメソッドを用意することができた。やったことはプロトコルへ興味を開げただけ。実装に手詰まっても、Apple のソースコードからヒントを得ることができる。時間があったら Swift の標準プロトコルを見て回るのも面白いかもしれない。