Swift方法调度异常:重写方法使用父类默认参数值
Swift重写方法默认参数的调度异常解析
问题描述
在Playground中出现异常行为:将B类实例赋值给A类型变量后调用execute()方法,输出为B:123,而非B类重写方法中定义的默认参数456。查看SIL文件与虚表后未发现明显问题,请问该异常调度的机制是什么?
代码示例
class A { func execute(param: Int = 123) { print("A: \(param)") } } class B: A { override func execute(param: Int = 456) { print("B: \(param)") } } let instance: A = B() instance.execute() // 输出结果:B: 123
SIL代码片段
// main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @riddle.instance : riddle.A // id: %2 %3 = global_addr @riddle.instance : riddle.A : $*A // users: %9, %8 %4 = metatype $@thick B.Type // user: %6 // function_ref B.__allocating_init() %5 = function_ref @riddle.B.__allocating_init() -> riddle.B : $@convention(method) (@thick B.Type) -> @owned B // user: %6 %6 = apply %5(%4) : $@convention(method) (@thick B.Type) -> @owned B // user: %7 %7 = upcast %6 : $B to $A // user: %8 store %7 to %3 : $*A // id: %8 %9 = load %3 : $*A // users: %12, %13 // function_ref default argument 0 of A.execute(param:) %10 = function_ref @default argument 0 of riddle.A.execute(param: Swift.Int) -> () : $@convention(thin) () -> Int // user: %11 %11 = apply %10() : $@convention(thin) () -> Int // user: %13 %12 = class_method %9 : $A, #A.execute : (A) -> (Int) -> (), $@convention(method) (Int, @guaranteed A) -> () // user: %13 %13 = apply %12(%11, %9) : $@convention(method) (Int, @guaranteed A) -> () %14 = integer_literal $Builtin.Int32, 0 // user: %15 %15 = struct $Int32 (%14 : $Builtin.Int32) // user: %16 return %15 : $Int32 // id: %16 } // end sil function 'main'
机制解析
这个现象的核心原因是:Swift的默认参数值是编译期静态绑定的,而非运行期动态派发。
默认参数的绑定逻辑:当省略参数调用方法时,编译器会根据变量的静态类型(即代码中声明的类型,这里是
A)选择对应的默认参数值,而非实例的实际运行时类型(B)。从SIL代码可见,%10引用的是A类execute方法的默认参数生成函数,调用后得到值123(对应%11)。方法的动态派发:默认参数是静态绑定,但方法本身的调用仍为动态派发。SIL中的
class_method指令会根据实例的实际类型(B)找到对应的方法实现,也就是B类重写的execute方法。最终执行流程:编译期确定默认参数值为
123,运行期动态调用B的execute方法并传入该参数,因此最终输出B:123。
简言之,默认参数值的选择与方法的动态派发是两个独立过程:默认参数由变量静态类型决定,方法实现由实例实际类型决定,二者结合产生了看似矛盾的输出结果。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

