在 Swift 中需要实现类似 Swizzling 的功能(如全局拦截、日志埋点、行为注入),但又想避开 Objective-C Runtime 的风险,可以根据不同的应用场景选择以下四种主流替代方案。
1. 基于协议扩展的“面向切面”(AOP via Protocol Extensions)
这是 Swift 最推荐的方式。利用协议扩展(Protocol Extension),你可以为一组类注入统一的行为,而不需要修改类内部的实现。
- 实现方式:定义一个协议,在扩展中实现默认方法,并让目标类遵守该协议。
- 优点:类型安全,不依赖运行时,支持
struct。
Swift
protocol Loggable {
func logAction(_ message: String)
}
extension Loggable where Self: UIViewController {
func trackScreenView() {
print("正在埋点统计:(type(of: self))")
}
}
// 目标类只需要声明遵守协议
extension HomeViewController: Loggable {}
// 在合适的时机手动调用(或配合生命周期)
//虽然不如 Swizzling 自动化,但逻辑极其显式、安全
2. 消息转发代理(Delegate Proxy)
如果你想拦截某个对象的特定行为(例如监听所有的 UIScrollView 滚动),可以使用代理转发技术。
- 实现方式:创建一个中间代理对象,拦截感兴趣的方法,然后将其他所有消息转发给原始代理。
- 适用场景:SDK 拦截、无侵入监听系统回调。
注:著名的响应式库 RxSwift 就大量使用了
DelegateProxy来拦截系统 UI 组件的事件,而没有使用 Swizzling。
3. 闭包注入(Closure Injection / Hooking)
对于你自己定义的类或者提供配置项的第三方库,可以使用“闭包作为钩子”的设计模式。
- 实现方式:在类中定义一个可选的闭包,在方法执行前后调用它。
Swift
class NetworkManager {
// 定义一个钩子
static var onDataRequest: ((URL) -> Void)?
func fetchData(url: URL) {
// 触发钩子逻辑(类似 Swizzling 的注入点)
NetworkManager.onDataRequest?(url)
// 原始逻辑...
}
}
// 在 AppDelegate 中注入全局逻辑
NetworkManager.onDataRequest = { url in
print("全局拦截网络请求: (url)")
}
4. 属性观察者(Property Observers)
如果你只想拦截状态的变化(这是 Swizzling 的一大用途),Swift 原生的 didSet 和 willSet 是最安全的替代品。
- 实现方式:在属性重写中使用观察者。
- 高级技巧:配合 Key-Value Observing (KVO) 。在 Swift 中,KVO 依然有效且比 OC 更安全(使用
NSKeyValueObservation),它在底层也是一种类似 Swizzling 的动态替换技术(isa-swizzling),但由系统官方维护。
5. 方案对比与选择建议
| 方案 | 是否依赖 Runtime | 侵入性 | 适用范围 | 推荐等级 |
|---|---|---|---|---|
| 协议扩展 | 否 | 低 | 所有的类、结构体 | ⭐⭐⭐⭐⭐ |
| 闭包注入 | 否 | 中 | 业务代码控制权在自己手中时 | ⭐⭐⭐⭐ |
| 代理转发 | 是/否 | 中 | 拦截系统组件(如 TableView)行为 | ⭐⭐⭐ |
| KVO | 是 | 低 | 监听属性状态变化 | ⭐⭐⭐ |
| Swizzling | 是 | 极高 | 必须全局拦截且无法修改源码时 | ⭐ (慎用) |
总结:2026 年的思维转换
在 Objective-C 时代,我们习惯于“在系统背后搞小动作”;而在 Swift 时代,我们更倾向于“通过显式的架构设计留出接口”。
- 如果是为了埋点:建议使用协议扩展。
- 如果是为了解耦逻辑:建议使用依赖注入(Dependency Injection)。
- 如果是为了修改系统组件:优先考虑继承或组合。