在 Swift 中,@convention(c) 是一个函数类型修饰符,表示该函数遵循 C 语言的调用约定(Calling Convention),也就是编译器安排参数、返回值、栈帧的方式必须与 C 兼容。
它通常用于:
1、与 C 语言或 C 库交互时。
2、将 Swift 的函数/闭包转换成可以传递给 C 函数的函数指针。
3、使用系统 API(如 macOS 的 AXObserverCreate、CFRunLoopSourceCreate 等)时,要求提供 @convention(c) 函数指针。
使用示例
public func AXObserverCreate(_ application: pid_t, _ callback: AXObserverCallback, _ outObserver: UnsafeMutablePointer<AXObserver?>) -> AXError
public typealias AXObserverCallback = @convention(c) (AXObserver, AXUIElement, CFString, UnsafeMutableRawPointer?) -> Void
这段代码定义了一个函数类型AXObserverCreate,它的callback参数是一个符合 C 调用约定的函数指针:
@convention(c) (AXObserver, AXUIElement, CFString, UnsafeMutableRawPointer?)
在Swift中,只有以下两种类型符合C 调用约定的函数指针:
1、全局函数;
2.、符合C调用约定的闭包;
类型方法(static func、class func)无法作为C函数指针使用。
1)错误用法,如果我们在Class中定义方法:
func callback(observer: AXObserver, element: AXUIElement, notification: CFString, context: UnsafeMutableRawPointer?) {
print("Received AX notification: \(notification)")
}
函数传给C函数时,就会报错:
A C function pointer can only be formed from a reference to a 'func' or a literal closure
报错的原因在于,C 函数指针必须是全局地址的函数指针,而 Swift 中的实例方法其实是带有隐藏参数(self)的动态方法,不能直接转换为 @convention(c) 的函数指针。
func callback(...) { } // 实际上是 (self: AXMonitor) -> (...) -> Void
在调用时,Swift 自动把 self 传给它,这跟 C 的调用方式不一致(C 函数指针不接受 self)。
关键点:实例方法不能符合 C 的函数指针要求,因为它不是纯粹的函数地址。
2)如果改为属性的话:
let callback: AXObserverCallback = { observer, element, notification, refcon in
print("接收到辅助功能通知")
}
就可以把这个函数指针传给 C 函数,比如:
AXObserverCreate(pid, Self.callback, &observer)
这是因为这个闭包其实是Swift 全局函数闭包的等价写法,Swift 会将其自动桥接为符合 @convention(c) 的函数指针,相当于手动声明了一个:
typealias AXObserverCallback = @convention(c) (AXObserver, AXUIElement, CFString, UnsafeMutableRawPointer?) -> Void
所以,这个闭包可以被编译器桥接为 C 调用约定的函数指针。
使用场景
1、AXObserverCreate;
2、CFRunLoopSourceCreate;
3、qsort(标准 C 排序函数);
4、Core Foundation 中要求 C 函数指针的 API;
5、注册回调函数到 C 语言库。
系统定义:很多系统 API,比如 AXObserverCallback、CFRunLoopPerformCallBack、CGEventTapCallBack 等等,都要求用 @convention(c) 修饰的函数指针,这是因为它们底层是用 C 或 Core Foundation 写的,使用 C 调用约定(Calling Convention)。
自己定义:也可以自己声明函数类型使用 @convention(c),例如:
typealias MyCFunc = @convention(c) (Int32) -> Void
func callCFunc(_ fn: MyCFunc) {
fn(42)
}
注意:@convention(c)只能用于闭包函数定义或者函数类型的类型注释:
// 使用 @convention(c) 正确定义函数类型
static let callback: @convention(c) AXObserverCallback = { observer, element, notification, context in
print("收到 AX 通知: \(notification)")
}
不可以用在Swift的方法声明上:
@convention(c) // 报错:Attribute can only be applied to types, not declarations
static func callback(observer: AXObserver, element: AXUIElement, notification: CFString, context: UnsafeMutableRawPointer?) {
print("Received AX notification: \(notification)")
}
注意事项
@convention(c) 闭包不能捕获上下文(即不能访问外部变量,闭包不能包含Self)。
参数和返回值类型必须是 C 可以识别的类型,如指针、整数、Void 等。
总结
@convention(c) 是一个函数类型修饰符,表示该函数遵循 C 语言的调用约定(Calling Convention),通常用于与C 语言相关内容交互时使用。