我刚制作了一个简单的测试应用程序来显示击键的键码以及修饰符。它适用于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.
}
}
}
答案 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)
}
}
// ...
}
另一种选择是安装/卸载事件处理程序
viewDidAppear
和viewDidDisappear
。