我有从Swift使用的C API。
在Swift中,我有:
enum GetSnapshotResult {
case success(snapshot: UIImage, String)
case failure()
}
func getSnapshot(completion: @escaping (GetSnapshotResult) -> Void) {
CAPIGetSnapshot(nil) { (_) in
completion(
.success(
snapshot: UIImage(),
"test"
)
)
}
}
在C中:
void CAPIGetSnapshot(void * ptr, void(*callbackOnFinish)(void *)) {
//do something in background thread
//and on its finish, call callbackOnFinish from thread
callbackOnFinish(ptr);
}
然而,我得到:
不能通过捕获上下文的闭包形成C函数指针
如何解决这个问题?
您需要一个包装器类,以便可以通过C函数将指向实例的void指针传递给回调。passRetained()
和takeRetainedValue()
的组合确保仅在调用完成函数之后才释放包装器实例。
func getSnapshot(completion: @escaping (GetSnapshotResult) -> Void) {
class Wrapper {
let completion: (GetSnapshotResult) -> Void
init(completion: @escaping (GetSnapshotResult) -> Void) {
self.completion = completion
}
}
let wrapper = Wrapper(completion: completion)
let observer = UnsafeMutableRawPointer(Unmanaged.passRetained(wrapper).toOpaque())
CAPIGetSnapshot(observer) { theObserver in
let theWrapper = Unmanaged<Wrapper>.fromOpaque(theObserver!).takeRetainedValue()
theWrapper.completion(
.success( snapshot: UIImage(), "test")
)
}
}
一些备注:
我假设C函数将
ptr
参数传递给回调。passRetained(wrapper)
保留对象。这确保了当getSnapshot()
函数返回时包装器实例不会被释放。takeRetainedValue()
在闭包中消耗retain。因此,当闭包返回时,包装器实例将被释放。completion
是闭包,闭包是引用类型。只要包装器实例存在,wrapper.completion
就持有对该闭包的引用。当然你可以在闭包中使用相同的变量名(" observer ", " wrapper ")。我在这里选择了不同的名称("theObserver","theWrapper"),只是为了强调它们是不同的变量,即闭包不再捕获上下文。
observer
需要是一个可变原始指针,只是因为C函数的第一个参数被声明为void * ptr
。如果您可以将函数声明更改为void CAPIGetSnapshot(const void * ptr, void(*callbackOnFinish)(const void *))
那么
let observer = UnsafeRawPointer(...)
也可以。有关对象引用到void指针之间转换的更多信息,请参见示例如何将self转换为UnsafeMutablePointer
输入swift.
代替自定义包装类,您还可以利用这样一个事实,即任意Swift值在转换为AnyObject
时自动装箱在类类型中(参见例如AnyObject在Xcode8 beta6中不工作?)。
func getSnapshot(completion: @escaping (GetSnapshotResult) -> Void) {
let wrapper = completion as AnyObject
let observer = UnsafeRawPointer(Unmanaged.passRetained(wrapper).toOpaque())
CAPIGetSnapshot(observer) { theObserver in
let theCompletion = Unmanaged<AnyObject>.fromOpaque(theObserver!).takeRetainedValue()
as! ((GetSnapshotResult) -> Void)
theCompletion(
.success( snapshot: UIImage(), "test")
)
}
}
强制展开和强制强制类型转换在这里是安全的,因为知道传递给函数的内容。如果无法展开包装或强制转换,则表明存在编程错误。但我更喜欢第一个版本,而不是依赖于这个"魔法"。