在全局队列中停止NSRunLoop

时间:2014-10-30 09:58:09

标签: ios objective-c grand-central-dispatch nstimer nsrunloop

我刚刚使用NSRunLoop中的NSTimerViewController创建了一个带有计时器的后台任务:

- (void)runBackgroundTask: (int) time{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(startTrackingBg) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });
}

调用一个能验证令牌有效性等的函数。是否可以从函数内部结束此循环?例如:

-(void)startTrackingBg
{
    if(TOKEN IS NOT VALID)
    {
        STOP_THREAD;
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self alertStatus:@"Session Lost!" :@"Error!"];
            [self popToLogin];
        });
    }
}

2 个答案:

答案 0 :(得分:3)

有几点想法:

  1. 如果查看run的文档,它们会显示一个可以解决问题的模式:

      

    如果没有输入源或定时器附加到运行循环,则此方法立即退出;否则,它通过反复调用NSDefaultRunLoopModerunMode:beforeDate:中运行接收器。换句话说,这种方法有效地开始了一个无限循环,它处理来自运行循环的输入源和定时器的数据。

         

    从运行循环中手动删除所有已知输入源和计时器并不能保证运行循环将退出。 OS X可以根据需要安装和删除其他输入源,以处理针对接收者线程的请求。因此,这些来源可以防止运行循环退出。

         

    如果希望运行循环终止,则不应使用此方法。相反,使用其他运行方法之一,并在循环中检查您自己的其他任意条件。一个简单的例子是:

    BOOL shouldKeepRunning = YES;        // global
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    
         

    其中shouldKeepRunning在程序中的其他位置设置为NO

  2. 话虽如此,如果我要启动另一个线程,我不会使用其中一个全局工作线程,而是我只是实例化我自己的NSThread

  3. 更重要的是,根据你在另一个线程中尝试做的事情,通常会有比建立自己的运行循环更好的其他模式。

    例如,如果我想让计时器在另一个队列中运行某些东西,我会改为使用调度计时器:

    @property (nonatomic, strong) dispatch_source_t timer;
    

    然后实例化并启动调度计时器源以在指定的GCD队列上运行:

    dispatch_queue_t queue = dispatch_queue_create("com.domain.app.polltimer", 0);
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), kPollFrequencySeconds * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
    
    dispatch_source_set_event_handler(self.timer, ^{
        <#code to be run upon timer event#>
    });
    
    dispatch_resume(self.timer);
    

    或者,如果您想使用NSTimer,只需在主runloop上安排它,并让它调用的方法将耗时的任务分配给当时的后台队列。但是,无论哪种方式,我都会避免增加第二次运行循环的开销。

  4. 向您展示了在后台线程中使用计时器的更好方法,现在您描述了意图(轮询服务器),我实际上建议不要使用计时器。当您希望以某个固定间隔启动某些操作时,计时器非常有用。但在这种情况下,您可能希望在上一个请求完成之后的一段时间后启动下一个服务器请求。因此,在上一个请求的完成块中,您可能会执行以下操作:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20.0 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        <#code to initiate next request#>
    });
    
  5. 此外,我个人希望确保有一个非常令人信服的理由来轮询您的服务器。轮询总是看起来非常直观和合乎逻辑,但它是对用户的电池,CPU和数据计划的过度使用。在需要客户端快速响应服务器更改的情况下,通常有更好的体系结构(套接字,推送通知等)。

答案 1 :(得分:0)

您执行调度异步并在内部向runloop添加一个计时器以定期运行方法?您应该添加线程以确保一次使用所有并行系统。 ; - )

说真的:使用dispatch_after()并在块内决定是否要再次执行此操作。