Reputation: 9344
I am trying to pass an anonymous function into a method that uses a closure. I have read Swift: Pass data to a closure that captures context and actually use its referenced question (How to use instance method as callback for function which takes only func or literal closure) for part of the solution.
The problem is that these solutions are all working off of methods on a class.
This is the code I'm attempting to do:
func startListeningForEvents(onNameReceived: @escaping (String) -> Void) {
selfPtr = Unmanaged.passRetained(self)
self.tap = CGEvent.tapCreate(tap: CGEventTapLocation.cghidEventTap,
place: CGEventTapPlacement.tailAppendEventTap,
options: CGEventTapOptions.listenOnly,
eventsOfInterest: CGEventMask(CGEventType.leftMouseUp.rawValue),
callback: { proxy, type, event, refcon in
let mySelf = Unmanaged<AccController>.fromOpaque(refcon!).takeUnretainedValue()
let name = mySelf.onEventTap(proxy: proxy, type: type, event: event, refcon);
// This is what I can't figure how to access
onNameReceived(name);
return nil;
},
userInfo: selfPtr.toOpaque());
}
In the above example. onEventTap
is going to happen no matter who calls this class. I want the caller to choose what happens when a name is successfully grabbed. I get the error A C function pointer cannot be formed from a closure that captures context
.
Upvotes: 1
Views: 2138
Reputation: 9344
I marked Rob's answer as the one seeing as it better answers the original question. The solution I ended up using was this:
class MyClass {
typealias MyFunctionDefinition = (String) -> Void
private var onNameReceived: MyFunctionDefinition!
init(callback: @escaping MyFunctionDefinition) {
self.onNameReceived = callback;
}
// ... the code from my question above
callback: { proxy, type, event, refcon in
let mySelf = Unmanaged<AccController>.fromOpaque(refcon!).takeUnretainedValue()
let name = mySelf.onEventTap(proxy: proxy, type: type, event: event, refcon);
// This is what I can't figure how to access
self.onNameReceived(name);
return nil;
},
}
I am able to just pass the callback in the constructor but I imagine other people might want to solve for the same thing in a way where it's done via a callback in the call.
Upvotes: 0
Reputation: 386038
I am trying to pass an anonymous function into a method that uses a closure.
This is not precisely the case. CGEvent.tapCreate
is a wrapper for the C function CGEventTapCreate
, which takes a callback that is a C function. A C function is not a closure. A closure contains both code to execute and any values that it references. It is said to “close over” those values. A C function can't close over any values; it only has access to the arguments passed to it and to global variables.
To work around this behavior, a C API that takes a callback function usually also takes an opaque pointer (void *
in C parlance) which you can use to pass in whatever extra state you want. In the case of CGEvent.tapCreate
, that is the userInfo
or refcon
argument (the documentation refers to it by both names). You're passing self
as that argument, but you actually want to pass in two values: self
and onNameReceived
.
One way to solve this is by adding a new instance property to hold onNameReceived
for use by the callback, since you can access the instance property through the self
reference you recover from refcon
.
My preferred solution is to wrap the event tap in a class that lets you use a Swift closure as the handler.
class EventTapWrapper {
typealias Handler = (CGEventTapProxy, CGEventType, CGEvent) -> ()
init?(
location: CGEventTapLocation, placement: CGEventTapPlacement,
events: [CGEventType], handler: @escaping Handler)
{
self.handler = handler
let mask = events.reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
let callback: CGEventTapCallBack = { (proxy, type, event, me) in
let wrapper = Unmanaged<EventTapWrapper>.fromOpaque(me!).takeUnretainedValue()
wrapper.handler(proxy, type, event)
return nil
}
// I can't create the tap until self is fully initialized,
// since I need to pass self to tapCreate.
self.tap = nil
guard let tap = CGEvent.tapCreate(
tap: location, place: placement,
options: .listenOnly, eventsOfInterest: mask,
callback: callback,
userInfo: Unmanaged.passUnretained(self).toOpaque())
else { return nil }
self.tap = tap
}
private let handler: Handler
private var tap: CFMachPort?
}
With this wrapper, we can just use a regular Swift closure as the tap handler:
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func startListeningForEvents(onNameReceived: @escaping (String) -> Void) {
self.tap = EventTapWrapper(
location: .cghidEventTap, placement: .tailAppendEventTap,
events: [.leftMouseUp],
handler: { [weak self] (proxy, type, event) in
guard let self = self else { return }
onNameReceived(self.onEventTap(proxy: proxy, type: type, event: event))
})
}
func onEventTap(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> String {
return "hello"
}
private var tap: EventTapWrapper?
}
Please note that this is not a complete example (although it compiles). You also have to enable the tap (with CGEvent.tapEnable
) and add it to a run loop (using CFMachPortCreateRunLoopSource
and then CFRunLoopAddSource
). You also need to remove the source from the run loop in the wrapper's deinit
, lest it try to use the wrapper after the wrapper has been destroyed.
Upvotes: 1