You need a wrapper class so that a void pointer to the instance can be tunneled through the C function to the callback. The combination of passRetained()
and takeRetainedValue()
ensures that the wrapper instance is released only after the completion function has been called.
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")
)
}
}
Some remarks:
I am assuming that the C function passes the ptr
argument to the callback.
passRetained(wrapper)
retains the object. That ensures that the wrapper instance is not released when the getSnapshot()
function returns.
takeRetainedValue()
in the closure consumes the retain. As a consequence, the wrapper instance is released when the closure returns.
completion
is a closure and closures are reference types. wrapper.completion
holds a reference to that closure as long as the wrapper instance exists.
Of course you can use the same variable names (“observer”, “wrapper”) inside the closure. I chose different names here (“theObserver”, “theWrapper”) only to emphasize that they are different variables, i.e. that the closure does not capture context anymore.
observer
needs to be a mutable raw pointer only because the first argument of the C function is declared as void * ptr
. If you can change the function declaration to
void CAPIGetSnapshot(const void * ptr, void(*callbackOnFinish)(const void *))
then let observer = UnsafeRawPointer(...)
works as well.
For more information about conversion between object references to void pointers see for example How to cast self to UnsafeMutablePointer<Void> type in swift.
Instead of a custom wrapper class you can also take advantage of the fact that arbitrary Swift values are automatically boxed in a class type when cast to AnyObject
(see for example AnyObject not working in 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")
)
}
}
The forced unwraps and forced casts are safe here because you know what it passed to the function. A failure to unwrap or cast would indicate a programming error. But I would prefer the first version instead of relying on this “magic”.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…