はじめに
本記事は今月頭に開かれた Otemachi.swift x Kyobashi.swift 合同勉強会で発表した資料をベースに作成しております。
本題
常識かと思いますが、下記のような二つの protocol 宣言は全く別物だということを常に意識せねばなりません。
protocol SomeProtocol {
func printSelf()
}
extension SomeProtocol {
func printSelf() {
print("SomeProtocol")
}
}
protocol SomeProtocol {
//func printSelf()
}
extension SomeProtocol {
func printSelf() {
print("SomeProtocol")
}
}
一行だけの違いですが、protocol SomeProtocol
の定義に Interface の宣言があるかどうかで、後の extension での実装の振る舞いは完全に別物になります。どう違うかというと、下記の例を読めばわかりやすいかと
protocol SomeProtocol {
func printSelf()
}
extension SomeProtocol {
func printSelf() {
print("SomeProtocol")
}
}
class SomeClass: SomeProtocol {
func printSelf() {
print("SomeClass")
}
}
let sc = SomeClass()
sc.printSelf() // 出力:SomeClass
(sc as SomeProtocol).printSelf() // 出力:SomeClass
protocol SomeProtocol {
}
extension SomeProtocol {
func printSelf() {
print("SomeProtocol")
}
}
class SomeClass: SomeProtocol {
func printSelf() {
print("SomeClass")
}
}
let sc = SomeClass()
sc.printSelf() // 出力:SomeClass
(sc as SomeProtocol).printSelf() // 出力:SomeProtocol
ほとんど同じようなものですが、protocol SomeProtocol
の宣言に func printSelf()
を宣言した場合、(sc as SomeProtocol).printSelf()
の出力結果は class SomeClass
に実装したものになりますが、そうでない場合は extension SomeProtocol
に実装したものになります。
なぜこのような違いがあるかというと、protocol の宣言時に宣言した関数は、この protocol の Interface(以下 PI と呼びます)となります。この protocol を準拠した class もしくは struct は、すべてこの PI があることが保証されています。ただし、extension ではこの PI のデフォルト実装があるので、もし準拠した class か struct にこの PI の実装を書いてなければ、extension で定義した実装を流用します。一方、宣言時にこの関数を宣言せず、extension でだけこの関数を実装した場合、これは PI としてのデフォルト実装ではなく、この protocol 固有の Method(以下 PM と呼びます)となります。
従って、(sc as SomeProtocol).printSelf()
を呼び出す時、ProtocolA
の場合は PI を呼び出しているので、extension のデフォルト実装関係なく、本来の実装である SomeClass
の printSelf()
を呼び出すが、ProtocolB
の場合は PM を呼び出すことになります。
ちなみに PM の呼び出しはこちらのスライドを読めばわかる通り、静的 Dispatch となります。
発展
先ほど PI と PM の違いについて論じましたが、PM は静的 Dispatch なので何も難しいことはないですが、問題は PI の実装と class の継承が同時に入って来た場合どうなるのかというと、話がややこしくなります。
ちょっと複雑なコードになりますが、こちらをごらんください:
protocol SomeProtocol {
func printSelf()
}
extension SomeProtocol {
func printSelf() {
print("SomeProtocol")
}
}
class SomeClass: SomeProtocol {
}
class SomeSubClass: SomeClass {
func printSelf() {
print("SomeSubClass")
}
}
class Parent {
let someClass: SomeClass
// init(someClass: SomeClass) { ... }
func printSelf() {
someClass.printSelf()
}
}
let ssc = SomeSubClass()
let p = Parent(someClass: ssc)
ssc.printSelf() // 出力:SomeSubClass
p.printSelf() // 出力:?
/* 選択肢
A:SomeProtocol
B:SomeClass
C:SomeSubClass
*/
簡単に説明しますと、先ほどの ProtocolA の場合と同じように、protocol SomeProtocl
に func printSelf()
という PI を宣言しました。そして extension SomeProtocol
にこの printSelf()
のデフォルト実装をしました。ここで class SomeClass
を SomeProtocol
に準拠させました。ただし printSelf()
の独自実装を行なっていないので当然ながら printSelf()
は SomeProtocol
のデフォルト実装になります。ここでさらに SomeClass
を継承した SomeSubClass
を登場させます。継承なので当然 SomeSubClass
もスーパークラスと同じように SomeProtocol
に準拠しています。なのでこの SomeSubClass
に printSelf()
を独自実装させてみました。ここでさらに class Parent
という新たなクラスを登場させます。このクラスには someClass
という SomeClass
型のプロパティーがあり、Parent
が printSelf()
を呼び出すと、someClass.printSelf()
を実行させます。ここで Parent
の someClass
プロパティーが SomeSubClass
のインスタンス ssc
の場合、Parent
の printSelf()
の出力は果たしてどうなるのでしょうか?
実は発表のとき、この質問を当時現地にいた参加者に投げてみたところ、意外なことに正解者が一人もいませんでした(筆者の予想では多分 A と C で半々くらいになるんじゃないかと思ったのですが)…
ところでここまで読んだ読者の皆さんはどれが正解だと思いますか?
まあ正解を発表する前に、まず選択肢を見ていきましょう。A:SomeProtocol は PI のデフォルト実装、B:SomeClass は前のコードに書いた SomeClass
の独自実装ですがここでそもそも登場してないのでまず可能性としてはありえないです。そして最後の C:SomeSubClass は今回のコードで書いた SomeSubClass
の独自実装です。
ここで p.printSelf()
を呼び出している際、実行されているのは P#someClass.printSelf()
ですので、言い換えれば (ssc as SomeClass).printSelf()
と同価です。
普通クラスを継承したとき、きちんと正しい振る舞いをさせるために、仮に継承した親クラスとして実行しても、実際行われているのは自分自身の実装になります。例えば
class Super {
func printSelf() {
print("I'm Super!")
}
}
class Sub: Super {
override func printSelf() {
print("I'm Sub!")
}
}
let sub = Sub()
(sub as Super).printSelf() // 出力:I'm Sub!
こちらのコードを実行し、sub
を Super
として printSelf()
を呼び出しても、呼び出されているのは Sub
で実装した print("I'm Sub!")
です。ですので逆にいうと、ssc.printSelf()
であろうか、(ssc as SomeClass).printSelf()
であろうか、出力結果は同じ、SomeSubClass
が実装した print("SomeSubClass")
になるはず。なので、正解は C:SomeSubClass。そう思いませんか?
そう思うみなさんは実際このコードを Playground で実行して見ましょう。驚きの結果がおまちしております。
そう、本当の正解は A:SomeProtocol です。
なぜそうなったのでしょうか。実は class SomeClass
で func printSelf()
を独自実装しなかったため、SomeClass
の SomeProtocol
としての printSelf()
はすでに SomeProtocol
のデフォルト実装で確定されました。SomeSubClass
が実装した func printSelf()
は、SomeProtocol
の PI ではなく、SomeSubClass
特有の printSelf()
です。つまり SomeSubClass
の printSelf()
は、SomeProtocol
の printSelf()
とは全く無関係です。
その証拠に、SomeSubClass
の func printSelf()
実装に、override
キーワードがないことをご注意ください。SomeSubClass
は親のメソッドをオーバーライドしていないのです。だから、(ssc as SomeClass).printSelf()
を呼び出す場合、プログラムはそもそも ssc
には独自の printSelf()
実装があるのは知らず、SomeClass
の printSelf()
を呼び出し、しかし SomeClass
は独自の printSelf()
がないため、準拠した SomeProtocol
の printSelf()
のデフォルト実装を呼び出すことになります。
ですので当然、(ssc as SomeProtocol).printSelf()
を呼び出しても、結果は同じ print("SomeProtocol")
になります。
だからなに
この protocol の振る舞いを知らないと、バグを生み出す可能性が出てきます。例えば何かのクラスを作ったとき、そのクラスが準拠したプロトコルのデフォルト実装で OK だとしても、自分を継承したサブクラスがそのデフォルト実装では足りない場合があります。ですので継承のことを考えて、仮に自分がデフォルト実装で OK でも、きちんと独自実装してデフォルト実装を呼び出しましょう。(もちろん継承させないように final
をつけるのも一つの解決ですが
class SomeClass: SomeProtocol {
func printSelf() {
(self as SomeProtocol).printSelf()
}
}
余談
実はこの仕様、以前にもすでに一度混乱を起こしたことがあります