Mac应用程序窗口停止更新 [英] Mac application window stops updating

查看:202
本文介绍了Mac应用程序窗口停止更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Swift中编写Mac应用程序(目标10.9+,在Mavericks上使用Xcode 6 Beta 3),其中我有很多NSTextField(标签),通过修改它们的内容,每秒多次更新几次, .stringvalue来自后台线程.在不同的时间段(五分钟到2个小时之间的任何时间)内,这似乎都可以很好地工作,但是随后应用程序窗口似乎停止更新.文本停止更新,将鼠标悬停在左上角的交通灯"控件上不会显示符号,并且在文本框等中单击也不会突出显示该框/不会显示工字梁.但是,我不确定的进度轮会继续旋转,并且当我调整窗口的大小/最小化/缩放或在NSScrollView框中滚动时,窗口会在移动过程中更新.

I am writing a Mac application (target 10.9+, using Xcode 6 Beta 3 on Mavericks) in Swift where I have a number of NSTextFields (labels) updating several times per second for extended periods of time by modifying their .stringvalue from a background thread. This seems to work well for a varying duration of time (anywhere between five minutes to 2 hours), but then the application window seems to stop updating. The text stops updating, hovering over the 'stoplight' controls on the upper-left does not show the symbols, and clicking in text boxes, etc., does not highlight the box/show the I-beam. However, my indeterminate progress wheel DOES continue to spin, and when I resize/minimize/zoom the window, or scroll in an NSScrollView box, the window updates during the movement.

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

My first guess was that some sort of window buffer was being used instead of a live image, so I tried to force an update using window.update(), window.flushWindowIfNeeded(), and window.flushWindow(), all to no avail. Can someone please tell me what's going on, why my window stops updating, and how to fix this problem?

推荐答案

您的问题就在这里:

我有许多NSTextField(标签),每次更新几次 通过修改其.stringvalue延长时间 从后台线程.

I have a number of NSTextFields (labels) updating several times per second for extended periods of time by modifying their .stringvalue from a background thread.

在OSX(和iOS)中, UI更新必须在主线程/队列中发生.否则,这是未定义的行为;有时会起作用,有时会不起作用,有时会崩溃.

In OSX (and iOS), UI updates must occur in the main thread/queue. Doing otherwise is undefined behavior; sometimes it'll work, sometimes it won't, sometimes it'll just crash.

解决此问题的快速方法是仅使用 Grand Central Dispatch (GCD),可使用

A quick fix to your issue would be to simply use Grand Central Dispatch (GCD) to dispatch those updates to the main queue with dispatch_async like:

dispatch_async(dispatch_get_main_queue(), ^{
    textField.stringValue = "..."
});

此操作的简化版本将块/闭包({}之间的代码)放入队列,默认运行循环(在主线程/队列上运行)在每次循环中检查该队列.当运行循环在队列中看到一个新块时,它将弹出并执行它.另外,由于使用的是dispatch_async(而不是dispatch_sync),所以进行分派的代码不会阻塞; dispatch_async将使该块排队并立即返回.

The simplified version of what that does is it puts the block/closure (the code between {}) in a queue that the default run loop (which runs on the main thread/queue) checks on each pass through its loop. When the run loop sees a new block in the queue, it pops it off and executes it. Also, since that's using dispatch_async (as opposed to dispatch_sync), the code that did the dispatch won't block; dispatch_async will queue up the block and return right away.

注意::如果您尚未阅读有关GCD的文章,我强烈建议您查看也是OSX/iOS中一般并发的好选择触及GCD).

Note: If you haven't read about GCD, I highly recommend taking a look at this link and the reference link above (this is also a good one on general concurrency in OSX/iOS that touches on GCD).

每秒几秒钟的确不是那么多,所以本节可能有点过头了.但是,如果您每秒获得30-60次以上,那么它将变得很重要.

Several times a second really isn't that much, so this section is probably overkill. However, if you get over 30-60 times a second, then it will become relevant.

您不希望遇到这样的情况,即您正在排队的UI更新积压得比处理起来快.在这种情况下,用计时器更新NSTextField会更有意义.

You don't want to run in to a situation where you're queueing up a backlog of UI updates faster than they can be processed. In that case it would make more sense to update your NSTextField with a timer.

基本思想是将要显示在NSTextField中的值存储在某个中间变量中.然后,启动一个在十分之一秒左右的时间内在主线程/队列上触发功能的计时器.在该函数中,用存储在该中间变量中的值更新您的NSTextField.由于计时器已经在主线程/队列上运行,因此您将在正确的位置进行UI更新.

The basic idea would be to store the value that you want displayed in your NSTextField in some intermediary variable somewhere. Then, start a timer that fires a function on the main thread/queue tenth of a second or so. In that function, update your NSTextField with the value stored in that intermediary variable. Since the timer will already be running on the main thread/queue, you'll already be in the right place to do your UI update.

我将使用> 设置计时器.看起来像这样:

I'd use NSTimer to setup the timer. It would look something like this:

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所指出的那样,当您从多个线程访问同一数据时,应该考虑数据同步.

Note: as @adv12 pointed out, you should think about data synchronization when you're accessing the same data from multiple threads.

注意:您还可以使用

Note: you can also use GCD for timers using dispatch sources, but NSTimer is easier to work with (see here if interested).

使用这样的计时器可以使您的UI保持快速响应;无需担心将主线程尽可能保留为空".如果由于某种原因开始失去响应能力,只需更改计时器,以使其不会经常更新.

Using a timer like that should keep your UI very responsive; no need to worry about "leaving the main thread as empty as possible". If, for some reason, you start losing some responsiveness, simply change the timer so that it doesn't update as often.

更新:数据同步

正如@ adv12所指出的,如果要在后台线程上更新数据,然后使用它来更新主线程中的UI,则应同步数据访问.实际上,您可以通过创建串行队列并确保仅在分派给该队列的块中读取/写入数据来使用GCD轻松地做到这一点.由于串行队列一次只能执行一个块,因此按接收到块的顺序,可以保证只有一个代码块可以同时访问您的数据.

As @adv12 pointed out, you should synchronize your data access if you're updating data on a background thread and then using it to update the UI in the main thread. You can actually use GCD to do this rather easily by creating a serial queue and making sure you only read/write your data in blocks dispatched to that queue. Since serial queues only execute one block at a time, in the order the blocks are received, it guarantees that only one block of code will be accessing your data at the same time.

设置您的串行队列:

let dataAccessQueue = dispatch_queue_create("dataAccessQueue", DISPATCH_QUEUE_SERIAL)

围绕您的阅读和写作:

dispatch_sync(dataAccessQueue) {
    // do reads and/or writes here
}

这篇关于Mac应用程序窗口停止更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆