0

我刚刚制作了一个简单的测试应用程序来显示击键的键码和修饰符。它适用于 3 次击键,然后应用程序崩溃。当它崩溃时,调试控制台最后只显示(LLDB)。有什么建议可能导致这种情况吗?也许与线程或指针有关,但我不确定如何解决这个问题。我包括下面的代码。我真的很感激任何帮助!谢谢!

import Cocoa
import Foundation

class ViewController: NSViewController {

    @IBOutlet weak var textField: NSTextFieldCell!
    let speech:NSSpeechSynthesizer = NSSpeechSynthesizer()

    func update(msg:String) {
        textField.stringValue = msg
        print(msg)
        speech.startSpeaking(msg)
    }

    func bridgeRetained<T : AnyObject>(obj : T) -> UnsafeRawPointer {
        return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque())
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        DispatchQueue.global().async {
            func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {

                let parent:ViewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeRetainedValue()

                if [.keyDown].contains(type) {
                    let flags:CGEventFlags =     event.flags
                    let pressed = Modifiers(rawValue:flags.rawValue)
                    var msg = ""

                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlphaShift.rawValue)) {
                        msg+="caps+"
                    }
                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskShift.rawValue)) {
                        msg+="shift+"
                    }
                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskControl.rawValue)) {
                        msg+="control+"
                    }
                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskAlternate.rawValue)) {
                        msg+="option+"
                    }
                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskCommand.rawValue)) {
                        msg += "command+"
                    }
                    if pressed.contains(Modifiers(rawValue:CGEventFlags.maskSecondaryFn.rawValue)) {
                        msg += "function+"
                    }

                    var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
                    msg+="\(keyCode)"

                    DispatchQueue.main.async {
                        parent.update(msg:msg)
                    }

                    if keyCode == 0 {
                        keyCode = 6
                    } else if keyCode == 6 {
                        keyCode = 0
                    }

                    event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
                }
                return Unmanaged.passRetained(event)
            }

            let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)

            guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback:  myCGEventCallback, userInfo: UnsafeMutableRawPointer(mutating: self.bridgeRetained(obj: self))) else {
                print("failed to create event tap")
                exit(1)
            }
            let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
            CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
            CGEvent.tapEnable(tap: eventTap, enable: true)
            CFRunLoopRun()
        }
        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }

}
4

1 回答 1

2

主要问题是引用计数:在安装事件处理程序时创建对视图控制器的保留 引用,这只会发生一次。然后你在回调中使用一个引用,每个点击事件都会发生这种情况。因此,引用计数最终会降为零,并且视图控制器会被释放,从而导致崩溃。

最好将未保留的引用传递给回调,并注意在释放视图控制器时卸载事件处理程序。

此外,无需为 OS X 应用程序创建单独的运行循环,或异步调度处理程序创建。

使回调成为全局函数,而不是方法。用于 takeUnretainedValue()获取视图控制器参考:

func myCGEventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {

    let viewController = Unmanaged<ViewController>.fromOpaque(refcon!).takeUnretainedValue()
    if type == .keyDown {

        var keyCode = event.getIntegerValueField(.keyboardEventKeycode)
        let msg = "\(keyCode)"

        DispatchQueue.main.async {
            viewController.update(msg:msg)
        }

        if keyCode == 0 {
            keyCode = 6
        } else if keyCode == 6 {
            keyCode = 0
        }
        event.setIntegerValueField(.keyboardEventKeycode, value: keyCode)
    }
    return Unmanaged.passRetained(event)
}

在视图控制器中,保留对运行循环源的引用,以便您可以将其删除deinit,并用于 passUnretained()将指向视图控制器的指针传递给回调:

class ViewController: NSViewController {

    var eventSource: CFRunLoopSource?

    override func viewDidLoad() {
        super.viewDidLoad()

        let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
        let userInfo = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

        if let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap,
                                         options: .defaultTap, eventsOfInterest: CGEventMask(eventMask),
                                         callback: myCGEventCallback, userInfo: userInfo) {
            self.eventSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
            CFRunLoopAddSource(CFRunLoopGetCurrent(), self.eventSource, .commonModes)
        } else {
            print("Could not create event tap")
        }
    }

    deinit {
        if let eventSource = self.eventSource {
            CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, .commonModes)
        }
    }

    // ...

}

另一种选择是在 viewDidAppear和中安装/卸载事件处理程序viewDidDisappear

于 2016-11-20T16:53:44.953 回答