线程中的Swift 3 CFRunLoopRun?(Swift 3 CFRunLoopRun in Thread?)

编程入门 行业动态 更新时间:2024-10-28 15:19:27
线程中的Swift 3 CFRunLoopRun?(Swift 3 CFRunLoopRun in Thread?)

我只是做了一个简单的测试应用程序,以显示击键的键码和修饰符。 它适用于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. } } }

I just made a simple testing app to display keycode of keystrokes along with modifiers. It works fine for 3 keystrokes, then the app crashes. When it crashes, debug console just shows (LLDB) at the end. Any suggestion what might be causing this? Maybe something has to do with thread or pointer, but I'm not sure how I can fix this. I'm including the code below. I'd really appreciate any help! Thanks!

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

最满意答案

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

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

另外,不需要为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安装/卸载事件处理程序。

The main problem is the reference counting: You create a retained reference to the view controller when installing the event handler, this happens exactly once. Then you consume a reference in the callback, this happens for every tap event. Therefore the reference count drops to zero eventually and the view controller is deallocated, causing a crash.

Better pass unretained references to the callback, and take care that the event handler is uninstalled when the view controller is deallocated.

Also there is no need to create a separate runloop for an OS X application, or to asynchronously dispatch the handler creation.

Make the callback a global function, not a method. Use takeUnretainedValue() to get the view controller reference:

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

In the view controller, keep a reference to the run loop source so that you can remove it in deinit, and use passUnretained() to pass a pointer to the view controller to the callback:

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

Another option would be to install/uninstall the event handler in viewDidAppear and viewDidDisappear.

更多推荐

本文发布于:2023-07-23 22:34:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1238195.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:线程   Swift   Thread   CFRunLoopRun

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!