Swift - 将转义闭包传递给 C API 回调

Swift - pass escaping closure to C API callback

我从 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 中 API:

void CAPIGetSnapshot(void * ptr, void(*callbackOnFinish)(void *)) {
    //do something in background thread
    //and on its finish, call callbackOnFinish from thread 
    
    callbackOnFinish(ptr);
}

然而,我得到:

A C function pointer cannot be formed from a closure that captures context

我该如何解决这个问题?

您需要一个包装器 class 以便可以通过 C 函数将指向实例的空指针传送到回调。 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() 函数 returns.

    时不会被释放
  • takeRetainedValue() 在闭包中消耗保留。因此,包装器实例在闭包 returns.

    时被释放
  • completion 是闭包,闭包是引用类型。 wrapper.completion 只要包装器实例存在,就会持有对该闭包的引用。

  • 当然你可以在闭包中使用相同的变量名(“observer”、“wrapper”)。我在这里选择了不同的名称(“theObserver”、“theWrapper”)只是为了强调它们是不同的变量,即闭包不再捕获上下文。

  • observer 需要是一个 可变 原始指针,只是因为 C 函数的第一个参数被声明为 void * ptr。如果可以将函数声明更改为

    void CAPIGetSnapshot(const void * ptr, void(*callbackOnFinish)(const void *))
    

    那么 let observer = UnsafeRawPointer(...) 也可以。

  • 有关对象引用到 void 指针之间的转换的更多信息,请参见

除了自定义包装器 class,您还可以利用以下事实:任意 Swift 值在转换为 AnyObject 时会自动装箱在 class 类型中(参见示例 )。

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")
        )
    }
}

强制解包和强制转换在这里是安全的,因为您知道它传递给函数的内容。解包或转换失败将指示编程错误。但我更喜欢第一个版本,而不是依赖这个“魔法”。