如何防止鼠标事件导致macOS应用中的NSTimer冻结?
根本原因是你创建的定时器默认只绑定到NSDefaultRunLoopMode,而当按住按钮或拖拽滑块时,App的RunLoop会切换到NSEventTrackingRunLoopMode,此时默认模式下的定时器就会暂停执行。不需要子类化控件,只需要修改定时器的RunLoop模式即可解决问题。
具体实现步骤
- 不要使用系统提供的
scheduledTimer(withTimeInterval:...)方法创建定时器(这个方法默认把定时器加到NSDefaultRunLoopMode),而是手动创建定时器后,将其添加到RunLoop的common模式集合中。
Swift 代码示例:
// 定义定时器属性(需要持有引用,避免被释放) var updateTimer: Timer? // 在合适的地方(比如viewDidLoad)初始化并添加定时器 func setupTimer() { updateTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(updateTextFieldValue), userInfo: nil, repeats: true) // 将定时器添加到RunLoop的common模式 if let timer = updateTimer { RunLoop.current.add(timer, forMode: .common) } } // 定时器触发的方法 @objc func updateTextFieldValue() { // 更新你的NSTextField数值逻辑 yourTextField.stringValue = "\(Date())" } // 记得在合适的时机(比如viewWillDisappear)销毁定时器 func teardownTimer() { updateTimer?.invalidate() updateTimer = nil }
闭包形式的定时器(Swift 3.2+):
var updateTimer: Timer? func setupTimer() { updateTimer = Timer(timeInterval: 0.1, repeats: true) { [weak self] _ in guard let self = self else { return } // 更新数值逻辑 self.yourTextField.stringValue = "\(Date())" } if let timer = updateTimer { RunLoop.current.add(timer, forMode: .common) } }
原理说明
RunLoop.Mode.common是一个模式集合,包含了default和eventTracking这两种常用模式。把定时器加到这个集合后,无论App处于正常事件处理模式,还是控件拖拽/按住的事件追踪模式,定时器都会正常触发,完全不需要修改任何控件的子类实现。
内容的提问来源于stack exchange,提问作者pw2
相关产品推荐
相关产品推荐

