Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
174 views
in Technique[技术] by (71.8m points)

Swift - pass escaping closure to C API callback

I have C API that I use from Swift.

In Swift, I have:

enum GetSnapshotResult {
    case success(snapshot: UIImage, String)
    case failure()
}

func getSnapshot(completion: @escaping (GetSnapshotResult) -> Void) {
    CAPIGetSnapshot(nil) { (_) in 
        completion(
            .success(
                snapshot: UIImage(),
                "test"
            )
        )
    }
}

And in C API:

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

However, with this I get:

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

How do I solve this?

question from:https://stackoverflow.com/questions/65860970/swift-pass-escaping-closure-to-c-api-callback

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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”.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...