如何在不暂停应用程序更新循环的情况下允许用户调整NSSlider?

时间:2013-08-29 11:53:40

标签: objective-c multithreading macos cocoa

注意:更新如下......

我有一个cocoa桌面应用程序,它包含一系列围绕自定义NSView的控件。我正在使用displayLink来推动更新。

当用户点击NSControl(滑块,按钮,复选框,单选按钮)时,应用程序似乎会冻结,直到释放鼠标。事实上,我可以确认displayLink回调(getFrameForTime)在此期间没有被触发。如果我创建一个也不会触发的计时器,则在用户释放鼠标之前,两者都会暂停,此时应用程序将继续更新。

控件被绑定,如果我从另一个线程更新该值(例如,通过MIDI接口的回调),滑块的行为与预期一致:它移动,值更新,应用程序不会暂停。

我觉得这应该是一个相当明显的修复,但我很难过。

  • 检查IB中的“连续”是否与广告一样:连续发送值,但在释放鼠标之前仍然会出现此行为(阻止UI更新)。

  • 这似乎与NSControl上的mouseDown有关?为什么会阻塞,我真的需要将所有UI元素子类化以改变这种行为(看起来极端)

  • DisplayLink是在自己的线程中,那么为什么mouseDown会在主线程上阻塞呢?如果是这种情况,考虑到从主线程以外更新Cocoa UI的禁令,我该如何处理它?<​​/ p>

任何帮助都非常感激。

更新

Per @ Nikolai的评论如下,我可以确认使用NSTimer并将其添加到NSEventTrackingRunLoopMode不会阻止。但是,我真的想使用CVDisplayLink(根据文档)在它自己的线程中运行,不应该以这种方式阻止。与CADisplayLink不同,我找不到一种方法来明确地将一个runloop分配给CVDisplayLink(它似乎不能那样工作),所以也许新的问题应该是:

为什么CVDisplayLink会阻止NSEventTrackingRunLoopMode?

2 个答案:

答案 0 :(得分:3)

单击NSControl时,只要鼠标按下,runloop模式就会从NSDefaultRunLoopMode变为NSEventTrackingRunLoopMode。这意味着只运行已添加到此模式的循环源(显示链接)和定时器。

您可以使用-[NSRunLoop addTimer:forMode:]将计时器添加到任何模式。对于显示链接,等效方法为-[CADisplayLink addToRunLoop:forMode:]

要在事件跟踪期间继续播放动画,您可以执行以下操作:

[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop]
                    forMode:NSEventTrackingRunLoopMode];

答案 1 :(得分:2)

您的测试项目显示您正在显示链接的回调中调用视图的display方法。

当评论display消息时,即使在移动滑块时也会连续调用显示链接。

所以出现问题的是当runloop进入事件跟踪模式时,对显示链接的线程上的display的调用会阻塞,直到释放鼠标并且运行循环返回到默认模式。您可以通过在调用之前放置一个日志语句并在之后放置一个日志语句来轻松确认。

为什么到底发生这种情况对我来说并不清楚。很清楚的是,从后台线程调用视图的方法是非法的。您必须通过在主线程上调度setNeedsDisplay:来触发视图的显示:

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [(__bridge MyCustomView*)displayLinkContext setNeedsDisplay:YES];
    });
    return kCVReturnSuccess;
}