Mac应用程序窗口停止更新

时间:2014-08-06 19:44:17

标签: macos user-interface swift window buffer

我正在Swift中编写一个Mac应用程序(目标10.9+,在Mavericks上使用Xcode 6 Beta 3),其中我有许多NSTextField s(标签)每秒更新几次,持续时间延长一段时间从后台线程修改.stringvalue。这似乎适用于不同的持续时间(在5分钟到2小时之间),但随后应用程序窗口似乎停止更新。文字停止更新,将鼠标悬停在“红绿灯”上。左上角的控件不显示符号,单击文本框等,不会突出显示框/显示工字梁。但是,我的不确定进度轮继续旋转,当我调整窗口大小/最小化/缩放窗口或在NSScrollView框中滚动时,窗口会在移动过程中更新。

我的第一个猜测是使用某种窗口缓冲区而不是实时图像,因此我尝试使用window.update()window.flushWindowIfNeeded()window.flushWindow()强制进行更新无济于事。有人可以告诉我发生了什么,为什么我的窗口停止更新,以及如何解决这个问题?

1 个答案:

答案 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
}