Posted at

[Swift]protocol extensionの罠

More than 1 year has passed since last update.

Swiftのプロトコルは便利なのでよく使うのですが、今になってハマったところがあったので。


optionalの代わりにextensionを使う

SwiftのプロトコルではObjective-Cにあったoptionalが廃止されました。

まあ、Objective-Cのoptionalはコンパイラがエラー吐かなくなるだけで、呼び出す際に毎回呼び出せるかチェックが必要だったのでかなり使い勝手悪かったですけど。

でも、Swiftでoptionalのような使い方ができないわけではなく、extensionでデフォルト動作を実装することで、optionalと同じような使い方ができます。

protocol SomeProtocol{

// required method
func r()

// optional method
func o()
}

extension SomeProtocol {
// optional method
func o() {
print("o() in SomeProtocol")
}
}

class SomeClass: SomeProtocol {
func r() {
print("r() in SomeClass")
}
}

let sc = SomeClass()
sc.r() // 出力 "r() in SomeClass"
sc.o() // 出力 "o() in SomeProtocol"

ここまでは問題ありません。

問題は、僕が"extensionにoptionalにしたい関数を書けばいい"と覚えていたことです。


protocolにメソッド定義を書かない

そんなふうに覚えていた僕は下のようにプロトコルを定義したんです。

protocol SomeProtocol{

// required method
func r()
}

extension SomeProtocol {
// optional method
func o() {
print("o() in SomeProtocol")
}
}

プロトコル定義からメソッドo()の定義が抜けています。

これでもコンパイルは通ります。

けど、動作が少し違うんです。

下のコードみたいな感じで。

protocol SomeProtocol{

// required method
func r()

// optional method
func o()
}

extension SomeProtocol {
// optional methods
func o() {
print("o() in SomeProtocol")
}
func p() {
print("p() in SomeProtocol")
}
}

class SomeClass: SomeProtocol {
func r() {
print("r() in SomeClass")
}
func o() {
print("o() in SomeClass")
}
func p() {
print("p() in SomeClass")
}
}

let sc: SomeClass = SomeClass()
sc.r() // 出力 "r() in SomeClass"
sc.o() // 出力 "o() in SomeClass"
sc.p() // 出力 "p() in SomeClass"
let sp: SomeProtocol = sc
sp.r() // 出力 "r() in SomeClass"
sp.o() // 出力 "o() in SomeClass"
sp.p() // 出力 "p() in SomeProtocol"

protocolとextension両方にメソッド定義してあるo()は常にクラスのメソッドが実行されるのに対し、

extensionにしか定義してないメソッドp()は、変数の型によって呼び出されるメソッドが違います。

同名の呼び出せるメソッドが複数ある時の優先順位が変わるんですかね。

Swiftのメソッド呼び出しの仕組みをよく知らないのでわかんないですけど。

今回たまたま実行されていないのに気づいたけど、過去に同じようなことやってバグを作ってないか心配。

というわけで、みなさんもお気をつけて。