为什么带有可选参数的此方法不能覆盖基类方法?

时间:2019-06-19 09:21:18

标签: swift

结果是“ a”,但我希望它是“ b”。我想知道为什么以及如何在不带参数的情况下调用doTest来打印“ b”。

class AA {
    func doTest() {
        print("a")
    }
}

class BB: AA {
    func doTest(_ different: Bool = true) {
        print("b")
    }
}

let bObjc = BB()
bObjc.doTest()

3 个答案:

答案 0 :(得分:11)

BB不会覆盖AA中的方法,这意味着BB上存在两个方法:

func doTest() // inherited
func doTest(_ different: Bool = true) // declared

致电时

bObjc.doTest()

编译器必须选择其中之一。并且它比没有参数的方法更喜欢没有参数的方法。

答案 1 :(得分:9)

得到a输出的原因是因为您没有调用期望func doTest(_ different: Bool = true)的新方法Bool

bObjc.doTest(true)  // b

您实际上正在做的是调用从父类doTest()继承的方法AA


如果要覆盖AA中的方法,则只需执行以下操作:

class BB: AA {
    override func doTest() {
        print("b")
    }
}

let bObjc = BB()
bObjc.doTest()

答案 2 :(得分:3)

这是一个不幸的情况。没有办法影响编译器如何用普通的Swift消除调用的歧义。

让我们看看一些选项:

  1. 确认编译器的工作,并覆盖BB.doTest()以将调用转发给BB.doTest(_:)
  2. 使用#selector(BB.doTest(_:))指定所需方法的签名
  3. 使用Swift协议限制编译器正在考虑的可能方法

覆盖类似名称的基本方法(可能是最简单的解决方案)

在这种情况下,强制编译器不使用最简单的方法签名的最简单方法是覆盖doTest中的B并重新路由调用:

class AA {
    func doTest() {
        print("a")
    }
}

class BB: AA {
    override func doTest() {
        self.doTest(true)
    }

    func doTest(_ different: Bool = true) {
        print("b")
    }
}

let bObjc = BB()
bObjc.doTest()

如果那不可能,请继续阅读。

指定doTest(_:)选择器(ObjC)

您可以指定是否要通过选择器调用doTest()doTest(_:)。但是,只有将方法注释为@objc并使类型从NSObject继承时,才可以由其选择器调用方法。因此,在实践中这可能会过分杀人。

import Foundation

class AA: NSObject {
    @objc func doTest() {
        print("a")
    }
}

class BB: AA {
    @objc func doTest(_ different: Bool = true) {
        print("b")
    }
}

let bObjc = BB()
bObjc.performSelector(onMainThread: #selector(BB.doTest(_:)), with: nil, waitUntilDone: true)

使用协议消除歧义

通过拆分进一步定义doTest()doTest(_:)的类型,可以帮助编译器选择正确的解决方案。使用协议,然后将doTest消息的接收方强制转换为协议,以使编译器知道应使用该协议中定义的方法。

步骤1:提取协议

协议本身:

protocol DoTestSpecific {
    func doTest(_ different: Bool)
}

不幸的是,您不能在协议的方法 definitions 中指定默认参数值。只有实现可以定义默认参数。

步骤2:使用BB类中的协议

投射(bObjc as DoTestSpecific)将告诉编译器完全不考虑AA.doTest。由于该协议未指定默认参数,因此您现在必须在呼叫站点上提供该值:

class AA {
    func doTest() {
        print("a")
    }
}

class BB: AA, DoTestSpecific {
    func doTest(_ different: Bool = true) {
        print("b")
    }
}

let bObjc = BB()
(bObjc as DoTestSpecific).doTest(false)

第3步:将实现转移到协议扩展

强制转换(bObjc as DoTestSpecific)的结果不知道BB的方法实现以及该方法附带的默认参数值。

但是您可以将实现转移到协议扩展,从而使不需要参数的实现甚至对于转换结果来说都是已知的!

最终代码:

class AA {
    func doTest() {
        print("a")
    }
}

protocol DoTestSpecific {
    func doTest(_ different: Bool)
}

extension DoTestSpecific {
    func doTest(_ different: Bool = true) {
        print("b")
    }
}

class BB: AA, DoTestSpecific {
}

let bObjc = BB()
(bObjc as DoTestSpecific).doTest()

这按预期工作。它要求添加协议和协议扩展中的实现。但是现在呼叫站点上已经不再有歧义了。

如果示例代码更加复杂,并且包含对对象状态或其他对象的依赖关系,则可能很难甚至无法实现。