动态方法调用是指在运行时(runtime)而非编译时按照名称调用方法的能力。在 Swift 中,这可以通过与 Objective-C runtime 的互动来实现,依赖于修饰符 @objc 和 dynamic,或者使用协议 AnyObject。
机制:
@objc — 标记方法/属性/类以导出到 Objective-C runtime。dynamic — 要求通过 Objective-C 消息派发执行,而不是通过静态调用(vtable)。AnyObject 协议允许将方法声明为可选,并允许通过可选链调用它们。限制:
@objc 的协议。@objc 属性的方法/属性或通过继承自 NSObject 标记的方法/属性。示例:
import Foundation class Person: NSObject { @objc dynamic func sayHello() -> String { return "Hello!" } } let person = Person() // 通过 Selector (perform:) 的动态调用: if person.responds(to: #selector(Person.sayHello)) { if let result = person.perform(#selector(Person.sayHello))?.takeUnretainedValue() as? String { print(result) // "Hello!" } }
类似地,可以通过 AnyObject 进行可选链调用:
@objc protocol Greeter { @objc optional func greet() } class Robot: NSObject, Greeter { func greet() { print("Beep-beep!") } } let anyGreeter: AnyObject = Robot() anyGreeter.greet?() // "Beep-beep!"
结构体能否通过 @objc、dynamic 或 AnyObject 支持动态方法调用?
答案: 不,只有继承自 NSObject 的类或具有 @objc 修饰符的类/协议才支持这些机制。结构体和枚举与 Objective-C runtime 不兼容,因此它们不能作为动态参与者。
故事
在一个项目中,使用了 @objc 协议作为委托。一个开发者移除了委托类继承自 NSObject 的继承,结果可选方法无法通过可选链调用。架构停止工作,测试失败频繁。
故事
在尝试在结构体的属性上使用 KVO(键值观察)的过程中,开发者将其标记为 dynamic。代码编译成功,但未按预期工作,因为结构体不支持 Objective-C runtime。对变化的动态反应没有生效,导致 UI 更新遗漏。
故事
在类的扩展中,忘记为通过 perform(_: ) 调用的函数添加 @objc 修饰符,结果在生产环境中试图调用未关联的选择器时发生崩溃。经过长时间的搜索原因,最终发现缺少函数导出到 Objective-C。