Как я могу передать анонимный метод в замыкание, которое захватывает контекст в Swift? - PullRequest
0 голосов
/ 10 апреля 2019

Я пытаюсь передать анонимную функцию в метод, который использует замыкание.Я прочитал Swift: передача данных в замыкание, которое захватывает контекст , и фактически использует его вопрос ( Как использовать метод экземпляра в качестве обратного вызова для функции, которая принимает только функцию или закрытие литерала ) длячасть решения.

Проблема в том, что все эти решения работают от методов класса.

Вот код, который я пытаюсь сделать:

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.

Ответы [ 2 ]

0 голосов
/ 10 апреля 2019

Я пометил ответ Роба как ответ, который лучше отвечает на первоначальный вопрос. Решение, которое я использовал в итоге, было таким:

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;

        },

}

Я могу просто передать обратный вызов в конструкторе, но я полагаю, что другие люди могут захотеть решить ту же проблему так, как это делается с помощью обратного вызова в вызове.

0 голосов
/ 10 апреля 2019

Я пытаюсь передать анонимную функцию в метод, который использует замыкание.

Это не совсем так.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 оболочки, чтобы он не пытался использовать оболочку после того, как оболочка была уничтожена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...