This is an unfortunate case. There's no way to influence how the compiler disambiguates the call with plain Swift like that.
Let's look at some options:
- Acknowledge what the compiler does and override
BB.doTest() to forward the call to BB.doTest(_:)
- Use
#selector(BB.doTest(_:)) to specify the desired method's signature
- Use Swift protocols to limit the possible methods the compiler is considering
Override similarly-named base method (probably the simplest solution)
The easiest way to force the compiler to not use the simplest method signature in this case is to override doTest in B and re-route the call:
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()
If that's not possible, read on.
Specify the doTest(_:) selector (ObjC)
You can specify if you want to call doTest() or doTest(_:) via selectors. You can only call methods by their selectors if you annotate the method as @objc and make the type inherit from NSObject, though. So this might be overkill in practice.
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)
Disambiguate using protocols
You can help the compiler pick the correct solution by splitting the type where doTest() and doTest(_:) are defined up further. Use a protocol and then cast the receiver of the doTest message to the protocol to let the compiler know it should use consider the method defined in the protocol.
Step 1: Extract the protocol
The protocol itself:
protocol DoTestSpecific {
func doTest(_ different: Bool)
}
Unfortunately, you cannot specify default argument values in method definitions in protocols. Only the implementation may define default arguments.
Step 2: Use the protocol in the BB class
Casting (bObjc as DoTestSpecific) will tell the compiler to not consider AA.doTest at all. Since the protocol specifies no default parameter, though, you have to provide the value on the call site for now:
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)
Step 3: Move implementation to protocol extension
The result of the cast (bObjc as DoTestSpecific) does not know about BB's implementation of the method and the default parameter value that goes with it.
But you can move the implementation to a protocol extension and thus make an implementation that doesn't require parameters known even for the result of the cast!
Final code:
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()
This works as expected. It requires the addition of a protocol and an implementation in a protocol extension. But now there's no ambiguity on the call site anymore.
If the example code was more complex and included dependencies on object state or other objects, it probably would be way harder or even impossible to make this work.