如何将匿名方法传递到捕获Swift中上下文的闭包中?

时间:2019-04-10 15:57:30

标签: swift

我正在尝试将匿名函数传递给使用闭包的方法。我已阅读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

2 个答案:

答案 0 :(得分:0)

  

我正在尝试将匿名函数传递给使用闭包的方法。

情况并非如此。 CGEvent.tapCreate是C函数CGEventTapCreate的包装,该函数接受C函数的回调。 C函数不是闭包。闭包包含要执行的代码和它引用的任何值。据说可以“封闭”这些价值。 C函数不能覆盖任何值;它只能访问传递给它的参数和全局变量。

要变通解决此问题,具有回调函数的C API通常还需要一个不透明的指针(在C语言中为{void *),您可以使用该指针传递任何所需的额外状态。在CGEvent.tapCreate的情况下,即userInforefcon自变量(文档用两个名称都引用了它)。您正在传递self作为该参数,但实际上您想传递两个值:selfonNameReceived

解决此问题的一种方法是添加新的实例属性以保存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;

        },

}

我能够只在构造函数中传递回调,但是我想其他人可能想通过调用中的回调来解决同一件事。