2016-11-20 69 views
0

我刚做了一个简单的测试应用程序,以显示击键的键码和修饰符。它适用于3个按键,然后应用程序崩溃。当它崩溃时,调试控制台在最后显示(LLDB)。任何建议可能会造成这种情况?也许某事与线程或指针有关,但我不知道如何解决这个问题。我包含下面的代码。我非常感谢任何帮助!谢谢!Swift 3 CFRunLoopRun in Thread?

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. 
     } 
    } 

} 

回答

0

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

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

另外,不需要为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

+0

非常感谢你这样的细节答案! – jl303

+0

其实我只是修改了代码,但现在它似乎没有触发事件,也不会引发任何错误。看来我不能使用这里的评论粘贴正确格式的整个代码。你愿意看看修订后的代码吗?对不起,我刚开始学习迅速。 http://pastebin.com/raw/tBJKnrKs – jl303

+0

@ jl303:它在我的测试中有效,但我必须以root身份运行该应用程序,否则轻按创建将失败。据记载,'tapCreate'只能以root用户身份运行,或者如果启用了辅助设备访问权限才能正常工作。 –