2016-11-20 143 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用戶身份運行,或者如果啓用了輔助設備訪問權限才能正常工作。 –