How to use instance method as callback for function which takes only func or literal closure
Solution 1
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.
Solution 2
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)
}
![Admin](/assets/logo_square_200-5d0d61d6853298bd2a4fe063103715b4daf2819fc21225efa21dfb93e61952ea.png)
Admin
Updated on June 04, 2022Comments
-
Admin about 2 years
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"
-
nsinvocation over 8 yearsI'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.
-
Saurav Nagpal almost 8 yearsHi @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.
-
Martin R almost 8 years@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.
-
Felix over 7 yearsIf 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.
-
Hemang about 7 yearsYou 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?
-
Martin R about 7 years@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: stackoverflow.com/a/30788165/1187415.
-
ixany about 5 yearsDoesn’t work for me (Swift 5, Xcode 10.2):
A C function pointer cannot be formed from a local function that captures context