线程中的Swift 3 CFRunLoopRun?

时间:2016-11-20 15:54:09

标签: swift multithreading cocoa swift3 cgeventtap

我刚制作了一个简单的测试应用程序来显示击键的键码以及修饰符。它适用于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.
        }
    }

}

1 个答案:

答案 0 :(得分:1)

主要问题是引用计数:您创建保留 在安装事件处理程序时引用视图控制器,这恰好发生一次。 然后你在回调中使用一个引用,每次都会发生这种情况 点击事件。因此,引用计数最终会降至零 取消分配视图控制器,导致崩溃。

更好地传递未回复的对回调的引用,并注意这一点 取消分配视图控制器时,将卸载事件处理程序。

此外,无需为OS X应用程序创建单独的runloop,也不需要异步调度处理程序创建。

使回调成为全局函数,而不是方法。使用 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)
        }
    }

    // ...

}

另一种选择是安装/卸载事件处理程序 viewDidAppearviewDidDisappear

相关问题