You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的默认参数值是编译期静态绑定的,而非运行期动态派发。

  1. 默认参数的绑定逻辑:当省略参数调用方法时,编译器会根据变量的静态类型(即代码中声明的类型,这里是A)选择对应的默认参数值,而非实例的实际运行时类型(B)。从SIL代码可见,%10引用的是A类execute方法的默认参数生成函数,调用后得到值123(对应%11)。

  2. 方法的动态派发:默认参数是静态绑定,但方法本身的调用仍为动态派发。SIL中的class_method指令会根据实例的实际类型(B)找到对应的方法实现,也就是B类重写的execute方法。

  3. 最终执行流程:编译期确定默认参数值为123,运行期动态调用B的execute方法并传入该参数,因此最终输出B:123。

简言之,默认参数值的选择与方法的动态派发是两个独立过程:默认参数由变量静态类型决定,方法实现由实例实际类型决定,二者结合产生了看似矛盾的输出结果。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:35:26