快速传递转义闭包到C API回调



我有从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")
)
}
}
强制展开和强制强制类型转换在这里是安全的,因为知道传递给函数的内容。如果无法展开包装或强制转换,则表明存在编程错误。但我更喜欢第一个版本,而不是依赖于这个"魔法"。

最新更新