【问题标题】:How can I allow a user adjust an NSSlider without pausing the application update loop?如何允许用户在不暂停应用程序更新循环的情况下调整 NSSlider?
【发布时间】:2013-09-01 20:19:14
【问题描述】:

注意:以下更新...

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

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

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

我觉得这应该是一个相当明显的解决方法,但我很难过。

  • 在 IB 中检查“连续”会像宣传的那样:连续发送值,但在释放鼠标之前仍然表现出这种行为(阻止 UI 更新)。

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

  • DisplayLink 是在自己的线程中,那为什么mouseDown 在主线程上阻塞呢?如果是这种情况,鉴于禁止从主线程以外的地方更新 Cocoa UI,我该如何处理?

非常感谢任何帮助。

更新

根据下面@Nikolai 的 cmets,我可以确认使用 NSTimer 并将其添加到 NSEventTrackingRunLoopMode 不会阻塞。但是,我真的很想使用 CVDisplayLink (根据文档)在它自己的线程中运行并且不应该以这种方式被阻止。与 CADisplayLink 不同,我找不到将运行循环显式分配给 CVDisplayLink 的方法(似乎它不起作用),所以也许新问题应该是:

为什么 CVDisplayLink 在 NSEventTrackingRunLoopMode 上会阻塞?

【问题讨论】:

  • 你能发布一个展示所描述行为的最小示例项目吗?
  • 好的,现在把它放在一起......
  • 好的,项目在这里。请注意,它确实非常小,它不显示任何内容(从计时器和 displayLink 观察 NSLog 的控制台)还请注意,滑块什么都不连接,什么也不做:dl.dropboxusercontent.com/u/930604/Personal/…

标签: objective-c multithreading macos cocoa


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢您为我指明了一个好的方向......这似乎描述了问题(并且有助于我理解正在发生的事情)但我使用的是 OSX 而不是 iOs,所以我使用的是 CVDisplayLink 而不是 CADisplayLink。 CVDisplayLink developer.apple.com/library/mac/documentation/QuartzCore/… 似乎没有提供指定运行循环的方法。我会继续挖掘。
  • @andrew 哦,我明白了。自从我在 Mac OS 上使用它已经有好几年了,我的印象是 Apple 添加了一种 Cocoa 方式来使用显示链接。使用漂亮的CADisplayLink 在 iOS 上要容易得多。如果我没记错的话,CVDisplayLink 使用自己的线程进行回调。我不明白为什么运行循环会影响这一点。对不起。
  • 我可以确认这确实解决了 NSTimers 的问题,所以我相信您已诊断出问题,我只是挂断了如何访问 MacOS 上的 displayLink 计时器对象(现在浏览文档,但是是的,它似乎不像 CADisplayLink 那样直接......它应该使用自己的线程。hrm。也许我会把所有东西都移植到 iphone ;))
【解决方案2】:

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

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

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

我不清楚为什么会发生这种情况。很明显,从后台线程调用视图的方法是非法的。您必须通过在主线程上调度 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;
}

【讨论】:

  • 非常感谢! 1.奇怪。但是... 2. 老实说,我不记得为什么我在那里调用 display (我的代码从苹果样板开始,并在我工作时发生了一些变化),我不需要它。非常感谢您的所有帮助,这让我很恼火。
  • 不客气。这是rubber ducking 真正有帮助的情况之一。有时橡皮鸭就是这样。
  • 我刚点了一只鸭子。:D 是的......显然从这段代码开始太久没有帮助。
猜你喜欢
  • 2022-12-10
  • 2017-08-01
  • 2011-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-02
  • 2016-07-23
相关资源
最近更新 更多