我正在Swift中编写一个Mac应用程序(目标10.9+,在Mavericks上使用Xcode 6 Beta 3),其中我有许多NSTextField
s(标签)每秒更新几次,持续时间延长一段时间从后台线程修改.stringvalue
。这似乎适用于不同的持续时间(在5分钟到2小时之间),但随后应用程序窗口似乎停止更新。文字停止更新,将鼠标悬停在“红绿灯”上。左上角的控件不显示符号,单击文本框等,不会突出显示框/显示工字梁。但是,我的不确定进度轮继续旋转,当我调整窗口大小/最小化/缩放窗口或在NSScrollView
框中滚动时,窗口会在移动过程中更新。
我的第一个猜测是使用某种窗口缓冲区而不是实时图像,因此我尝试使用window.update()
,window.flushWindowIfNeeded()
和window.flushWindow()
强制进行更新无济于事。有人可以告诉我发生了什么,为什么我的窗口停止更新,以及如何解决这个问题?
答案 0 :(得分:4)
你的问题就在这里:
我有多个NSTextFields(标签)每次更新几次 通过修改.stringvalue来延长一段时间 来自后台主题。
在OSX(和iOS)中, UI更新必须发生在主线程/队列中。否则是不明确的行为;有时它会工作,有时它不会,有时它会崩溃。
对您的问题进行快速解决,只需使用Grand Central Dispatch(GCD)将这些更新通过dispatch_async
发送到主队列,如:
dispatch_async(dispatch_get_main_queue(), ^{
textField.stringValue = "..."
});
它的简化版本是将块/闭包(代码在{}
之间)放入队列中,默认运行循环(在主线程/队列上运行)检查每次传递它的循环。当运行循环在队列中看到一个新块时,它会将其弹出并执行它。此外,由于那是使用dispatch_async
(而不是dispatch_sync
),执行调度的代码不会阻塞; dispatch_async
将排队并立即返回。
注意:如果您还没有阅读过有关GCD的信息,我强烈建议您查看this链接,上面的参考链接(this也是关于触摸GCD的OSX / iOS中的一般并发性的好方法。
编辑:每秒几次真的不是那么多,所以这部分可能有些过分。但是,如果你每秒超过30-60次,那么它将变得相关。
您不希望遇到这样一种情况,即您正在排队积压的UI更新,而不是处理它们的速度。在这种情况下,使用计时器更新NSTextField
会更有意义。
基本思想是将您希望在NSTextField
中显示的值存储在某个中间变量中。然后,启动一个计时器,它在十分之一秒左右的主线程/队列上触发一个函数。在该函数中,使用存储在该中间变量中的值更新NSTextField
。由于计时器已经在主线程/队列上运行,因此您已经在正确的位置进行UI更新。
我会使用NSTimer
来设置计时器。它看起来像这样:
var timer: NSTimer?
func startUIUpdateTimer() {
// NOTE: For our purposes, the timer must run on the main queue, so use GCD to make sure.
// This can still be called from the main queue without a problem since we're using dispatch_async.
dispatch_async(dispatch_get_main_queue()) {
// Start a time that calls self.updateUI() once every tenth of a second
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target:self, selector:"updateUI", userInfo: nil, repeats: true)
}
}
func updateUI() {
// Update the NSTextField(s)
textField.stringValue = variableYouStoredTheValueIn
}
注意:正如@ adv12指出的那样,当您从多个线程访问相同的数据时,您应该考虑数据同步。
注意:您还可以使用dispatch sources将GCD用于计时器,但NSTimer
更容易使用(see here if interested)。
使用这样的计时器应该保持你的UI非常敏感;无需担心“尽可能将主线程留空”。如果由于某种原因,您开始失去一些响应能力,只需更改计时器,使其不会经常更新。
更新:数据同步
正如@ adv12指出的那样,如果要更新后台线程上的数据,然后使用它来更新主线程中的UI,则应同步数据访问。实际上,您可以通过创建一个串行队列并确保只在分派到该队列的块中读取/写入数据来轻松地使用GCD。由于串行队列一次只执行一个块,按接收块的顺序,它保证只有一个代码块同时访问您的数据。
设置序列队列:
let dataAccessQueue = dispatch_queue_create("dataAccessQueue", DISPATCH_QUEUE_SERIAL)
用以下内容包围你的读写:
dispatch_sync(dataAccessQueue) {
// do reads and/or writes here
}