タイトルがわかりづらいですね、すみません。
あるプロトコル(例:Animalプロトコル)に準拠したクラスを大量に作っていたとします(例:犬、猫、猿、象、シマウマ)。そして別のプロトコル(例:Humanプロトコル)を定義している時に、
「それらのクラスのどれでもいいから、さっきのプロトコルに準拠したクラスを引数に持つ関数(例:飼う)を作りたい」
と思ったとします。この実装に少し手間取ったので、備忘録的に残します。
実装
まずはAnimalプロトコルと、それに準拠したクラスを大量に定義します。プロトコルにAnyObjectの継承をつけることで、Animalプロトコルはクラスにしか準拠できないようにしています。
protocol Animal: AnyObject { }
class Dog: Animal { }
class Cat: Animal { }
class Monkey: Animal { }
class Elephant: Animal { }
class Zebra: Animal { }
そしてHumanプロトコルを定義します。プロトコル内で、飼う(keep)メソッドを実装しています。
protocol Human: AnyObject {
associatedtype AnyAnimal: Animal
func keep(_ animal: AnyAnimal)
}
プロトコル内でassociatedtypeを定義しています。これは、そのプロトコル内でのみ使えるクラスを定義しているイメージです。今は「Animalプロトコルに準拠していれば」何のクラスでも良い、と条件があるので、AnyAnimalにはAnimalプロトコルを準拠させています。そしてメソッドの引数には、AnyAnimalを指定すればOKです。
使用例
上のプロトコルの使い方を、以下に示します。
class DogLover: Human {
typealias AnyAnimal = Dog
func keep(_ animal: AnyAnimal) {
print("I keep a " + String(describing: type(of: animal)))
}
}
class CatLover: Human {
typealias AnyAnimal = Cat
func keep(_ animal: AnyAnimal) {
print("I keep a " + String(describing: type(of: animal)))
}
}
Humanプロトコルに準拠したクラスを定義するとき、その中でtypealiasを定義する必要があります。これは、Humanプロトコル内で使っていたassociatedtypeのクラスは〇〇である、というのを宣言するイメージです。
上の例では、犬好きな人クラスと猫好きな人クラスを定義しました。それぞれ、犬好きな人クラスは中身に犬を飼うメソッドが、猫好きな人クラスは中身に猫を飼うメソッドが実装されています。
実際にインスタンスを用いて使うと、
let sampleDog = Dog()
let sampleCat = Cat()
let taro = DogLover()
taro.keep(sampleDog) // I keep a Dog
taro.keep(sampleCat) // エラー
let hanako = CatLover()
hanako.keep(sampleDog) // エラー
hanako.keep(sampleCat) // I keep a Cat
のように、それぞれ同じプロトコルに準拠したクラスでありながら、別々のクラスを引数にもつ関数が定義できていることがわかります。
ちなみに、
class Fish { }
class FishLover: Human {
typealias AnyAnimal = Fish // エラー
func keep(_ animal: AnyAnimal) {
print("I keep a " + String(describing: type(of: animal)))
}
}
のように、typealiasでAnimalプロトコルに準拠していないクラスを入れようとするとエラーになります。これは上でも説明したように、associatedtypeでAnyAnimalをAnimalプロトコルに準拠するように定義したからですね。
まとめ
正直、associatedtypeというものを知らなかったので色々時間がかかりましたが、associatedtypeで調べると記事はいろいろ出てきますね。次からはassociatedtypeという名前さえ覚えておけばなんとかなりそうです笑
追記(2019/11/14)
記事投稿後の関連記事に並んでるものを読みながら思ったのですが、ジェネリクスを使うともっと便利に書けましたね。
class AnimalLover<T: Animal>: Human {
typealias AnyAnimal = T
func keep(_ animal: AnyAnimal) {
print("I keep a " + String(describing: type(of: animal)))
}
}
let taro = AnimalLover<Dog>()
taro.keep(sampleDog) // I keep a Dog
taro.keep(sampleCat) // エラー
引数のクラスに関係なくメソッドがすべて共通の場合は、ジェネリクスを使ったほうが楽でした。