我正在尝试将匿名函数传递给使用闭包的方法。我已阅读Swift: Pass data to a closure that captures context,并实际上将其引用的问题(How to use instance method as callback for function which takes only func or literal closure)用于解决方案的一部分。
问题在于这些解决方案都无法使用类上的方法。
这是我尝试执行的代码:
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());
}
在上面的示例中。无论谁叫这个班,onEventTap
都会发生。我希望呼叫者选择成功获取名称后会发生什么。我收到错误A C function pointer cannot be formed from a closure that captures context
。
答案 0 :(得分:0)
我正在尝试将匿名函数传递给使用闭包的方法。
情况并非如此。 CGEvent.tapCreate
是C函数CGEventTapCreate
的包装,该函数接受C函数的回调。 C函数不是闭包。闭包包含要执行的代码和它引用的任何值。据说可以“封闭”这些价值。 C函数不能覆盖任何值;它只能访问传递给它的参数和全局变量。
要变通解决此问题,具有回调函数的C API通常还需要一个不透明的指针(在C语言中为{void *
),您可以使用该指针传递任何所需的额外状态。在CGEvent.tapCreate
的情况下,即userInfo
或refcon
自变量(文档用两个名称都引用了它)。您正在传递self
作为该参数,但实际上您想传递两个值:self
和onNameReceived
。
解决此问题的一种方法是添加新的实例属性以保存onNameReceived
供回调使用,因为您可以通过self
引用访问实例属性,而引用是从{{1}中恢复的}。
我首选的解决方案是将事件拍子包装在一个类中,该类允许您使用Swift闭包作为处理程序。
refcon
有了这个包装器,我们就可以使用常规的Swift闭包作为点击处理程序了:
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?
}
请注意,这不是一个完整的示例(尽管可以编译)。您还必须启用分接头(使用@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?
}
)并将其添加到运行循环中(使用CGEvent.tapEnable
然后使用CFMachPortCreateRunLoopSource
)。您还需要从包装器CFRunLoopAddSource
的运行循环中删除源,以免包装器被破坏后尝试使用包装器。
答案 1 :(得分:0)
我将Rob的答案标记为看到的答案,因为它可以更好地回答原始问题。我最终使用的解决方案是:
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;
},
}
我能够只在构造函数中传递回调,但是我想其他人可能想通过调用中的回调来解决同一件事。