在 Swift 中,@objc 关键字和运行时(Runtime)机制是两个重要的概念,分别服务于与 Objective-C 的互操作性和动态特性。以下是详细解析:
一、@objc 的作用
@objc 主要用于 桥接 Swift 代码与 Objective-C,使其能够被 Objective-C 调用或与 Objective-C 的动态特性兼容。具体作用包括:
-
暴露 Swift 类型到 Objective-C
-
类:若 Swift 类需被 Objective-C 调用,必须显式继承
NSObject或标记为@objc。若类名包含非 ASCII 字符(如中文),需通过@objc(ClassName)指定 Objective-C 兼容的 ASCII 名称1610。swift
@objc(MyClass) class 我的类: NSObject { @objc(greeting:) func 打招呼(名字: String) { ... } } -
协议:若协议包含
optional方法,必须标记为@objc,以支持 Objective-C 的可选方法特性110。swift
@objc protocol CounterDataSource { optional func increment() -> Int } -
枚举:仅原始值为整型的非泛型枚举可被
@objc修饰,以便在 Objective-C 中使用110。
-
-
重命名 Objective-C 符号
可通过@objc(Name)自定义方法或属性在 Objective-C 中的名称,例如解决命名冲突或适配命名规范610。 -
动态特性支持
- 标记为
@objc的成员可通过 Objective-C 运行时动态调用(如performSelector:)。 - 但
@objc不强制使用动态派发,若需完全动态行为(如 KVO、方法替换),需结合dynamic关键字67。
- 标记为
二、Swift 中的运行时机制
Swift 默认偏向静态派发以优化性能,但在特定场景下支持运行时动态特性:
-
动态派发
- 函数表派发(vtable) :类的非
final方法通过虚函数表实现动态派发,支持多态27。 - 消息派发:使用
@objc或dynamic修饰的方法,或继承自NSObject的类,可通过 Objective-C 的objc_msgSend实现消息派发67。
- 函数表派发(vtable) :类的非
-
运行时反射与动态加载
- 反射:通过
Mirror类型可获取类型元数据(如属性、方法),但功能有限,不推荐高性能场景使用2。 - 动态加载:使用
dlopen和dlsym动态加载库,或通过 Objective-C 运行时库(如objc_getClassList)遍历类信息5。
- 反射:通过
-
内存管理与 ARC
Swift 运行时通过自动引用计数(ARC)管理内存,处理对象的生命周期及引用关系(强/弱/无主引用)2。 -
动态特性示例
swift
class MyClass: NSObject { @objc dynamic func foo() { ... } // 支持 KVO 和方法替换 }dynamic强制方法使用消息派发,支持运行时干预(如 Method Swizzling)79。
三、使用场景对比
| 场景 | @objc 的作用 | 运行时机制的应用 |
|---|---|---|
| 与 Objective-C 互调 | 暴露 Swift 类、方法、属性到 Objective-C | 通过消息派发实现动态调用 |
| 实现可选协议方法 | 必须标记协议为 @objc | 使用 Objective-C 的运行时特性 |
| KVO 或方法替换 | 结合 dynamic 使用 | 依赖 Objective-C 的消息派发机制 |
| 减少包大小优化 | 避免不必要的 @objc 标记 | 静态派发减少运行时开销9 |
四、注意事项
- 性能权衡:动态派发(如消息机制)比静态派发慢,需在灵活性与性能间平衡27。
- ABI 稳定性:自 Swift 5 起,ABI 稳定,Swift 运行时库集成到操作系统中,减少了应用体积8。
- 混编限制:Swift 值类型(如结构体、枚举)无法直接暴露给 Objective-C,需通过
@objc类封装68。
总结
@objc是 Swift 与 Objective-C 互操作的桥梁,用于暴露符号、重命名及支持动态特性。- Swift 运行时 支持动态派发、反射和内存管理,但需通过
@objc或dynamic显式启用动态行为。 - 合理使用二者,可在保持 Swift 类型安全与性能的同时,灵活兼容 Objective-C 生态。