How to use instance method as callback for function which takes only func or literal closure

The callback is a pointer to a C function, and in Swift you can pass
only a global function or a closure (which does not capture any state),
but not an instance method.

So this does work:

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
        nil,
        { (_, observer, name, _, _) in
            print("received notification: \(name)")
        },
        "myMessage",
        nil,
        .DeliverImmediately)

But since the closure cannot capture context, you have no direct reference to self and its properties and instance methods.
For example, you cannot add

self.label.stringValue = "got it"
// error: a C function pointer cannot be formed from a closure that captures context

inside the closure to update the UI when a notification arrived.

There is a solution, but it is a little bit complicated due to
Swift’s strict type system.
Similarly as in Swift 2 – UnsafeMutablePointer<Void> to object, you can convert the pointer to
self to a void pointer, pass that as the observer parameter
to the registration, and convert it back to an object pointer in
the callback.

class YourClass { 

    func callback(name : String) {
        print("received notification: \(name)")
    }

    func registerObserver() {

        // Void pointer to `self`:
        let observer = UnsafePointer<Void>(Unmanaged.passUnretained(self).toOpaque())

        CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
            observer,
            { (_, observer, name, _, _) -> Void in

                // Extract pointer to `self` from void pointer:
                let mySelf = Unmanaged<YourClass>.fromOpaque(
                        COpaquePointer(observer)).takeUnretainedValue()
                // Call instance method:
                mySelf.callback(name as String)
            },
            "myMessage",
            nil,
            .DeliverImmediately)
    }

    // ...
}

The closure acts as a “trampoline” to the instance method.

The pointer is an unretained reference, therefore you must ensure
that the observer is removed before the object is deallocated.


Update for Swift 3:

class YourClass {

    func callback(_ name : String) {
        print("received notification: \(name)")
    }

    func registerObserver() {

        // Void pointer to `self`:
        let observer = UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())

        CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
            observer,
            { (_, observer, name, _, _) -> Void in
                if let observer = observer, let name = name {

                    // Extract pointer to `self` from void pointer:
                    let mySelf = Unmanaged<YourClass>.fromOpaque(observer).takeUnretainedValue()
                    // Call instance method:
                    mySelf.callback(name.rawValue as String)
                }
            },
            "myMessage" as CFString,
            nil,
            .deliverImmediately)
    }

    // ...
}

See also How to cast self to UnsafeMutablePointer<Void> type in swift for more information
about the “bridging” between object pointers and C pointers.

Leave a Comment