讨论来源: [Pitch] Allow default parameter overrides
这个帖子是 18 年的,截止目前 20.09.01,Xcode 自带 Swift 版本 5.2.4,情况仍然不变。
举个例子
class Foo {
func f(_ i: Int = 0) {
print(i, "Foo")
}
class SubFoo : Foo {
override func f(_ i: Int = 1) {
print(i, "SubFoo")
}
}
let foo = Foo()
foo.f() // 0 Foo
let subFoo = SubFoo()
subFoo.f() // 1 SubFoo
// subFoo 和 subFooAsFoo 明明指向的是同一个实例,调用的是同一个方法,结果居然会不同!
let subFooAsFoo = subFoo as Foo
subFooAsFoo.f() // 0 SubFoo
为什么会这样呢?
原理
首先,存在两条原则
- 方法由 Signature(主要是参数列表和返回值)和 Implementation(方法体)组成
- override 时,只改变 Implementation,不改变 Signature
OC 的 Runtime 也是依据这两条原则设计的。
那么,默认参数是属于 Signature,还是属于 Implementation 呢?
Signature
假如是属于 Signature,则 override 时指定的默认值应该是无效的,即
foo.f() // 0 Foo
let subFoo = SubFoo()
subFoo.f() // 0 SubFoo
// half and half
let subFooAsFoo = subFoo as Foo
subFooAsFoo.f() // 0 SubFoo
这与实际结果不符。
Implementation
假如是属于 Implementation,则 override 时指定的默认值应覆盖基类的默认值,即
foo.f() // 0 Foo
let subFoo = SubFoo()
subFoo.f() // 1 SubFoo
// half and half
let subFooAsFoo = subFoo as Foo
subFooAsFoo.f() // 1 SubFoo
同样与实际结果不符。
Swift 中的实现
Swift 中的实现 违背了原则 2:
override 时,只改变 Implementation,不改变 Signature
将 subFooAsFoo 指定为 Foo,调用 f() 时默认参数是 0,说明使用的 Signature 是基类的,即
Swift 中默认参数是属于 Signature 的。
这就意味着
class SubFoo : Foo {
// Override 时不仅修改了 f 的 Implementation,还影响到了 f 的 Signature
override func f(_ i: Int = 1) {
print(i, "SubFoo")
}
}
// 如果 Signature 不变,这里输出的应该是 "0 SubFoo"
let subFoo = SubFoo()
subFoo.f() // 1 SubFoo
一句话总结
Swift 中默认参数属于 func 的 Signature,但可以通过 override 来修改。
这种一半一半的实现,导致了
同一个实例,调用同一个方法,结果居然会不同!
的可怕后果。
目前实现下的最佳实践
// 这是我能想到的最好办法 - 不用默认参数,就不会有问题
class Foo {
// 如果不允许子类修改默认参数值,就加上 private
// private class var defaultI: Int {
class var defaultI: Int {
return 0
}
func f(_ i: Int) {
print(i, "Foo")
}
/// use defaultI as default parameters of f(_ i: Int)
func f() {
f(Self.defaultI)
}
}
class SubFoo : Foo {
override class var defaultI: Int {
return 1
}
override func f(_ i: Int) {
print(i, "SubFoo")
}
}
// 这种情况下,默认参数就属于 Implementation 了
let foo = Foo()
foo.f() // 0 Foo
let subFoo = SubFoo()
subFoo.f() // 1 SubFoo
let subFooAsFoo = subFoo as Foo
subFooAsFoo.f() // 1 SubFoo
这个做法的主要缺陷是不好处理多个默认参数。
可以考虑将多个默认参数抽取成一个单独的 Class 或是 Struct,将问题转换成单个默认参数的 case。