12-5.【OC】【Swift+OC 混编】Swift protocol 标记 @objc 后,在 OC 端如何调用?

0 阅读2分钟

当你在 Swift Protocol 前加上 @objc 后,它在 Objective-C (OC) 端的使用体验几乎与原生的 NSProtocol 一致。由于编译器生成了桥接元数据,OC 可以通过标准的 委托模式(Delegate Pattern)运行时反射 来调用它。

以下是具体的调用流程和底层细节:


1. 基础调用:委托模式 (Delegate)

这是最常见的场景。你只需像使用 OC 协议一样引用它。

  • Swift 端定义:

    Swift

    @objc protocol JSProtocol: AnyObject {
        func didCompleteTask(status: Int)
        @objc optional func optionalMethod()
    }
    
    @objc class TaskManager: NSObject {
        @objc weak var delegate: JSProtocol?
    }
    
  • OC 端调用:

    由于编译器生成了 -Swift.h,你只需导入它,然后像往常一样调用:

    Objective-C

    #import "YourProject-Swift.h"
    
    - (void)execute:(TaskManager *)manager {
        // 直接调用 required 方法
        [manager.delegate didCompleteTaskWithStatus:200];
    
        // 检查并调用 optional 方法
        if ([manager.delegate respondsToSelector:@selector(optionalMethod)]) {
            [manager.delegate optionalMethod];
        }
    }
    

2. 命名转换规则 (Renaming)

Swift 方法名在映射到 OC 时会发生变化,这会影响你在 OC 端的写法:

  • 参数标签: 第一个参数后的标签会成为方法名的一部分。

    • Swift: func process(data: Data, count: Int)
    • OC: - (void)processWithData:(NSData *)data count:(NSInteger)count;
  • 显式命名: 如果你想在 OC 中使用特定的名称,可以使用 @objc(...)

    • Swift: @objc(execWithID:) func execute(id: Int)
    • OC: [obj execWithID:123];

3. 底层调用链:从 objc_msgSend 到 Swift 实现

当你从 OC 端发起调用时,发生了一系列底层的“接力”:

  1. 消息派发: OC 调用 objc_msgSend(delegate, @selector(didCompleteTaskWithStatus:), 200)

  2. 查找 IMP: Runtime 在该 Swift 类的 OC 元数据(由 @objc 插入)中查找到对应的 Thunk(存根) 函数地址。

  3. 进入 Thunk: 这是一个编译器生成的中间函数。

    • 参数转换: 它将 NSInteger 转回 Swift 的 Int,将 NSData 转回 Data
    • Self 转换: 确保 self 符合 Swift 的寄存器约定。
  4. 跳转实现: Thunk 内部调用真正的 Swift 原生函数实现。


4. 关键限制与差异

虽然 @objc 开启了桥接,但由于 OC 的局限性,以下行为会发生变化:

特性Swift 原生 Protocol标记 @objc 后的 Protocol
可选方法不支持(需通过协议扩展模拟)支持 @objc optional
遵循对象Struct, Enum, Class仅限 Class (必须继承自 AnyObject)
派发方式协议见证表 (PWT) 派发消息转发 (Message Dispatch)
性能较高(编译器可优化)较低(运行时动态查找)

5. 动态性增强:respondsToSelector:

标记为 @objc 的协议会自动获得 OC 的动态检查能力。在 Swift 中,你无法对非 @objc 协议使用 respondsToSelector:,但在 OC 中,这是调用 @optional 方法的强制性标准动作


💡 深度启发:为什么要小心使用?

在大型混编项目中,每增加一个 @objc protocol,编译器都会在生成的 -Swift.h 中增加一段代码,并在二进制文件中增加协议描述符。如果协议非常庞大,这会增加 App 的包体积(Binary Size)启动时的类加载耗时