はじめに
「Swiftでは、任意の型に対応させられるように、Genericsを使って型を抽象化できるのか。。。🧐」
「なんと、Protocolでもassosiated typeで任意の型を表現できるらしい!😳」
「。。。。。。。。。。。。。」
「で、どっち使えばいいんだ?🙄🙄」
※以下 PAT = Protocol associated type
検証 (Swift5, Xcode10.2.1)
例えば下記のようにポケモン(もはや恒例)を表す Pokemon
というプロトコルとそれに準拠した Pikachu
があるとする。
今回はポケモンの名前を出力してくれる PokemonDescriptor
を作りたい。
PokemonDescriptor
は、Pokemon
プロトコルに準拠しているものなら何でも出力できる。
protocol Pokemon {
var name: String { get }
}
struct Pikachu: Pokemon {
let name: String
}
PATを使った場合
protocol CharacterDescribable {
associatedtype Character
func describe(_ character: Character)
}
struct PokemonDescriptor: CharacterDescribable {
typealias Character = Pokemon
func describe(_ character: Character) {
print("\(character.name)だよ〜")
}
}
let pikachu = Pikachu(name: "ピカチュウ")
let pokeDescriptor = PokemonDescriptor()
pokeDescriptor.describe(pikachu)
// output: ピカチュウだよ〜
Genericsを使った場合
struct PokemonDescriptor {
func describe<T: Pokemon>(_ pokemon: T) {
print("\(pokemon.name)だよ〜")
}
}
let pikachu = Pikachu(name: "ピカチュウ")
let pokeDescriptor = PokemonDescriptor()
pokeDescriptor.describe(pikachu)
// output: ピカチュウだよ〜
何が違うのか💡
プログラム自体がしていることは同じ。
違いはそれぞれの describe()
メソッドが実行される方法(Method dispatch)。
Method dispatchとは
メソッドを呼び出すときにどの実装が実行されるかを選択/解決するシステム。
コンパイラがコンパイル時にそれを解決するのがstatic dispatch。実行時に解決するのはdynamic dispatch。
Qiita内だと、この記事とかSwiftのMethod dispatchについて詳しく書かれてます👏
GenericsとPATにおいて、前者で定義されたメソッドはstatic dispatchで実行されるが後者で定義されたメソッドはdynamic dispatchで実行される。
staticはコンパイル時にどのメソッドを走らせるか事前に決めるので実行速度が速い、ただしメモリーをより多く消費する。
逆にdynamicはプログラム実行中に動的に決定するので、その分実行速度は落ちるが効率的にメモリーを使用できる。
結論✍️
- ケースバイケース
- どちらでもよく、簡単なコードならとりあえず実行速度の速いGenericsを検討する
- ただし、PATの方が表現力や可読性が高い(そもそもSwiftはProtocol oriented)
- 自分はPATでやりたい
最後に
自分でもまだしっくり来てないです。誰かアドバイスください!!(説教でも可😜)
参考資料
SwiftにおけるMethod Dispatchについて - Qiita
Method Dispatch in Swift – RPLabs – Rightpoint Labs