如何 Override 带有默认参数的基类方法?

427 阅读2分钟

讨论来源: [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

为什么会这样呢?

原理

首先,存在两条原则

  1. 方法由 Signature(主要是参数列表和返回值)和 Implementation(方法体)组成
  2. 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。