37

In "ViewController.swift" I am creating this callback:

func callback(cf:CFNotificationCenter!, 
    ump:UnsafeMutablePointer<Void>, 
    cfs:CFString!, 
    up:UnsafePointer<Void>, 
    cfd:CFDictionary!) -> Void {

}

Using this observer:

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), 
    nil, 
    self.callback, 
    "myMESSage", 
    nil, 
    CFNotificationSuspensionBehavior.DeliverImmediately)

Results in this compiler error:

"A C function pointer can only be formed from a reference to a 'func' or a literal closure"

pkamb
  • 33,281
  • 23
  • 160
  • 191
user2203342
  • 379
  • 1
  • 3
  • 4

2 Answers2

50

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I've used your approach but still get error (" A C function pointer cannot be formed from a closure that captures context "). But when trying to call an instance method using mySelf, Xcode doesn't complain, it sees the method. – nsinvocation Nov 21 '15 at 12:28
  • Hi @MartinR, Thanks for the solution. But i think the statement in your answer "closure cannot capture context" is not valid. Closure can capture context but function poster or c function cannot. Please let me know if I misunderstood something. – Saurav Nagpal Jul 15 '16 at 06:22
  • 2
    @SauravNagpal: Only a global function or a closure which does not capture context can be passed to a C function as a function pointer argument. That's what I tried to express in the first sentence. – Martin R Jul 15 '16 at 06:39
  • If you need this solution for CFNotificationCenterGetDistributedCenter and target OS X 10.10 or above, you can also use NSDistributedNotificationCenter instead, which lets you pass a selector and has a nicer API. – Felix Apr 04 '17 at 11:07
  • You just marked my question as a duplicate of this, but our requirements are totally different. Should the question have the answer for the ops requirement? Can you add your comments on my answer with the solution? – Hemang May 30 '17 at 12:00
  • @Hemang: It is exactly the same problem (you cannot use "self" in the closure) and exactly the same solution: You have to convert self to a void pointer, pass that as context pointer to the callback (in your case using the last argument of CGPDFScannerCreate), and convert back to an instance pointer in the callback. – Here is another example of the same technique: https://stackoverflow.com/a/30788165/1187415. – Martin R May 30 '17 at 12:04
3

In my case the function I wanted to call from my closure was in the AppDelegate. So I was able to use a delegate to call the function from the closure without using self. Whether this is a good idea or not is something that someone with more experience will have to comment on.

        self.pingSocket = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_DGRAM, IPPROTO_ICMP,CFSocketCallBackType.dataCallBack.rawValue, {socket, type, address, data, info in
            //type is CFSocketCallBackType
            guard let socket = socket, let address = address, let data = data, let info = info else { return }

// stuff deleted, including use of C pointers

            let appDelegate = NSApplication.shared.delegate as! AppDelegate
            appDelegate.receivedPing(ip: sourceIP, sequence: sequence, id: id)
            //}
            return
        }, &context)

extension AppDelegate: ReceivedPingDelegate {
    func receivedPing(ip: UInt32, sequence: UInt16, id: UInt16) {
        // stuff deleted
    }
}
protocol ReceivedPingDelegate: class {
    func receivedPing(ip: UInt32, sequence: UInt16, id: UInt16)
}
Darrell Root
  • 724
  • 8
  • 19
  • 3
    Doesn’t work for me (Swift 5, Xcode 10.2): `A C function pointer cannot be formed from a local function that captures context` – ixany Apr 09 '19 at 17:21