ObjecTips

Swift & Objective-C で iOS とか macOS とか

Swift の関数の override で引数のデフォルト値を変更すると何が起こるか

Xcode engineer の人がこんなツイートをしていた

早速 Playground で確認

まずクラスとメソッドを定義*1

import Foundation

class Base {
    func test(i: Int = 1) {
        print("Base class i: \(i)")
    }
}

class Sub: Base {
    override func test(i: Int = 2) {
        print("Sub class i: \(i)")
    }
}

通常のインスタンス化と呼び出し

let base = Base()
base.test()
// Base class i: 1

let sub = Sub()
sub.test()
// Sub class i: 2

ここまでは普通


問題は次
サブクラスでインスタンス化しつつ親クラスを型として宣言
そしてメソッドを呼ぶと...

let a: Base = Sub()
a.test()
// Sub class i: 1

インスタンスはサブクラスの Sub でメソッドもサブクラスの Sub.test() が呼ばれるけど、デフォルト引数は親クラス Base で指定されている値が使われる!

以下を試してみる

let b = Sub()
b.test()
// Sub class i: 2

let c: Base = b
c.test()
// Sub class i: 1

print(b === c)
// true

同一インスタンスの参照であるため === で比較すると true になる。 つまり実体は1つ。
でも宣言型によってメソッドを呼んだ時に適用されるデフォルト引数が変化している。
個人的には直感に反した挙動でいつか落とし穴になってしまいそう。*2


ちなみに Kotlin だと親クラスでデフォルト引数が指定されていようがいまいが override された関数ではデフォルト引数を指定できない仕様になっているらしく、不意の事故が起こる心配が無い。


ドキュメントでの記述が見当たらなかったので Swift Forum を当たってみたところ、2018年3月に話題に上がっていた模様。*3

https://forums.swift.org/t/pitch-allow-default-parameter-overrides/10673

*1:シンプルにするため super.test() は省いた

*2:ドキュメントを探したけどこの挙動の仕様について明記されている場所は見つからず
https://docs.swift.org/swift-book/LanguageGuide/Functions.html#ID169
https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html#ID196

*3:内容は確認できていないけど C++ と同じ挙動だそう