使用NSNotification中断循环

时间:2011-10-27 12:22:18

标签: objective-c multithreading cocoa loops nsnotification

我有一个包含循环方法的类。如果发生某个事件(例如按下按钮),我需要能够打破循环。

我在按下按钮时使用NSNotificationCenter通知包含循环的类。

但是,如果我在执行循环时按下按钮,则会在循环完成后发出通知,而不是中断循环。

我猜这是因为它在同一个线程中运行。

那么如何在后台/不同的线程中运行NSNotificationCenter?这可能吗?或者有更好的方法吗?

4 个答案:

答案 0 :(得分:4)

这不仅仅是通知中心。

  

我有一个包含循环方法的类。如果发生某个事件(例如按下按钮),我需要能够打破循环。

按下按钮的事件进入主线程。如果您的循环在主线程上运行,那么按钮本身在循环完成之前不会被处理。相对于您的应用程序实际处理的按钮按钮,通知会立即发布。

或者,以列表形式:

  1. 用户按下按钮。
  2. 您的循环用完了要做的事情并返回。
  3. 按下按钮到达您的应用程序,然后通过该按钮转换为动作消息。
  4. 您发布通知。
  5. 您收到通知。
  6. 您所看到的延迟是在步骤1和2之间;第4步发生在第3步之后。

    本地(非分布式)NSNotificationCenter上的通知将在您发布的线程上发送,因此从您的操作方法发布它意味着它将在主线程上调度。这很正常,没问题。

    循环而不是通知移动到后台线程,调度队列或操作队列。如果使用操作队列,则可能根本不需要通知,因为您可以告诉操作队列取消所有挂起的操作。 (您的操作需要在任何适当的时间检查它们是否已被取消;对于reasons previously discussed,在随机时间杀死线程/操作是一个坏主意。)

    后台线程,块和操作可以在需要时(例如,更新UI)与主线程通信。要通过主线程的运行循环发送消息,请使用performSelectorOnMainThread:withObject:waitUntilDone:。要在主线程上调度块,请使用dispatch_asyncdispatch_get_main_queue。要在主线程上安排操作,请将其添加到[NSOperationQueue mainQueue]

    有关详情,请参阅Concurrency Programming GuideNotification Programming Topics

答案 1 :(得分:3)

我会在一个单独的线程中运行你的循环,并且有一个实例变量BOOL abort;,当你按下按钮通知时,设置abort = TRUE;然后在循环中检查这个值并退出,如果它是真。

答案 2 :(得分:3)

我会在一个单独的线程中运行循环。更好的是,将其设为NSOperation,以便您可以拨打[.. cancel]。从NSOperation对象更新UI时,请确保使用performSelectorOnMainThread。在主线程上运行一个长循环并不是一个好主意。

答案 3 :(得分:2)

您无法将通知中心放在另一个主题上。那个对象超出了你的控制范围。问题不在于它们与您不允许负责处理按钮按下的run loop执行任何操作的线程相同。 (每个线程只有一个运行循环。)正如edsko和Peter Hosey所说,按钮按下本身,实际上是整个UI,在循环运行时停止。通常一个好主意是将长时间运行的操作放到后台线程上,然后回调主线程来更新UI,performSelectorOnMainThread:withObject:waitUntilDone:是一种简单的方法来进行这样的回调。

也就是说,如果要将循环保留在主线程上,则需要让控制定期返回到运行循环,以便按下按钮。我有两种方法可以做到这一点。首先,您可以在循环的每次迭代期间明确地给出运行循环控件:

while( !buttonWasPressed ){

    // Do work...

    // Let the run loop do some processing before the next iteration.
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}

或者,您可以创建一个仅包含循环代码的方法,并使用performSelector:withObject:afterDelay:重复调用该方法,同时仍允许运行循环工作:

- (void) loopTheLoop {

    if( buttonWasPressed ) return;

    // Do work...

    // Run this method again as soon as possible.
    [self performSelector:@selector(loopTheLoop)
               withObject:nil
               afterDelay:0.0];
}