Я пытаюсь передать анонимную функцию в метод, который использует замыкание.
Это не совсем так.CGEvent.tapCreate
- это оболочка для функции C CGEventTapCreate
, которая принимает обратный вызов, который является функцией C.Функция переменного тока не является закрытием.Закрытие содержит как код для выполнения, так и любые значения, на которые оно ссылается.Говорят, что «закрыть» эти ценности.Функция AC не может закрываться по любым значениям;он имеет доступ только к переданным ему аргументам и глобальным переменным.
Чтобы обойти это поведение, C-API, который принимает функцию обратного вызова, обычно также принимает непрозрачный указатель (void *
на языке C), которыйВы можете использовать для перехода в любое дополнительное состояние, которое вы хотите.В случае CGEvent.tapCreate
это аргумент userInfo
или refcon
(документация ссылается на него обоими именами).Вы передаете self
в качестве этого аргумента, но на самом деле вы хотите передать два значения: self
и onNameReceived
.
Один из способов решить эту проблему - добавить новое свойство экземпляра для хранения onNameReceived
для использования обратным вызовом, поскольку вы можете получить доступ к свойству экземпляра через ссылку self
, которую вы восстанавливаете из 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?
}
С помощью этой обертки мы можем просто использовать обычное закрытие Swift в качестве обработчика крана:
@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
).Вам также необходимо удалить источник из цикла выполнения в deinit
оболочки, чтобы он не пытался использовать оболочку после того, как оболочка была уничтожена.