异步 NSURLConnection 中断计时器

Asynchronous NSURLConnection breaks timers

我设置了一个 class 来处理我的网络 API 呼叫。这是使用 NSMutableURLRequestNSRLlConnection 完成的。我最初使用 connectionWithRequest: delegate: 并且在大多数情况下效果很好,除非我依赖这个请求是真正异步的,而不仅仅是在主 运行 循环中部分执行。

为此,我想我会使用非常方便的 sendAsynchronousRequest: queue: completionHandler: 并且起初在我所有的单元测试中我认为它工作得很好。它异步执行,我的信号量被等待并正确发出信号,太棒了。

直到我尝试在我的实际应用程序中重新使用我的 Web 服务的这个新修改版本 class。我的应用程序的一部分播放视频并使用重复 NSTimer 根据视频的当前播放时间更新部分屏幕。由于某些未知原因,只要我至少执行了这些新的异步 NSURLConnection 之一,视频播放和计时器就不再工作。

这是我初始化连接的方式:

[NSURLConnection sendAsynchronousRequest:requestMessage
                                   queue:[[NSOperationQueue alloc] init]
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
                       {
                           if ( data.length > 0 && connectionError == nil )
                           {
                               _webServiceData = [data mutableCopy];
                               [self performSelector:@selector(connectionDidFinishLoading:) withObject:nil];
                           }
                           else if ( connectionError != nil )
                           {
                               _webServiceData = [data mutableCopy];
                               [self performSelector:@selector(webServiceDidFinishExecutingWithError:) withObject:connectionError];
                           }
                       }];

以下是我如何初始化重复计时器:

playbackTimeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkPlaybackTime) userInfo:nil repeats:YES];

而且我完全不知道为什么异步 NSURLConnection 会导致我的应用程序中与停止运行完全无关的方面。

编辑:

为了澄清,我有 ViewControllerA 执行网络请求以检索一些数据。成功检索到该数据后,ViewControllerA 会自动转到 ViewControllerBViewControllerBviewWillAppear 是我设置电影播放器​​和计时器的地方。

  1. 以后不要再用信号量让测试等待了。使用专为测试异步进程而设计的新 XCTestExpectation

    并且与传统的信号量技巧不同,使用测试期望不会阻塞主线程,因此如果您有需要主线程的完成块或委托,您可以结合测试期望来执行此操作。

  2. 您显然正在做一些不起作用的事情,因为您在后台队列中 运行ning 了它。典型问题包括

    • 如果您试图从后台线程 运行 该计时器,那是行不通的,除非您使用标准计时器解决方法之一(将其安排在主 运行循环,将定时器的创建分派回主线程,使用分派定时器等)。

      之前已经非常详细地描述了这些备选方案,结果证明您是从 viewWillAppear 启动计时器,所以这都是学术性的。

    • 任何 UI 更新(包括执行转场、重新加载表等)。

    • 同步从后台线程更新的 class 属性时应小心(这些也可能最好分派到主线程)。

    无论如何,您可以通过告诉 sendAsynchronousRequest 到 运行 它在主队列上的完成块来解决这个问题:

    [NSURLConnection sendAsynchronousRequest:requestMessage
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
        ...
    }]; 
    

    然后,完成块将在后台队列中有 运行,一切可能都会好起来。或者您可以手动将完成处理程序的调用分派回主队列。

    但要确保任何 UI 更新(包括以编程方式执行 segue)在主线程上 运行(通过 运行 在主线程上对整个完成块进行线程,或手动将相关调用分派到主线程)。