【问题标题】:Asynchronous NSURLConnection breaks timers异步 NSURLConnection 中断计时器
【发布时间】:2015-01-30 20:47:05
【问题描述】:

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

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

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

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

[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 会自动转到ViewControllerB。在ViewControllerBviewWillAppear 中,我设置了我的电影播放器​​和计时器。

【问题讨论】:

  • 为什么要使用performSelector:withObject:[self performSelector:@selector(connectionDidFinishLoading:) withObject:nil];)而不是直接调用方法([self connectionDidFinishLoading:nil])?
  • 我一定是在想我需要在主线程上做,却忘了切换回来
  • performSelector:withObject: 将在当前线程上执行该方法,而不是在主线程上。您的意思是改用performSelectorOnMainThread:withObject:waitUntilDone: 吗?
  • 应该是这样,我可以试试切换到主线程,或者直接调用方法

标签: ios objective-c asynchronous nsurlconnection


【解决方案1】:
  1. 以后不要使用信号量让测试等待。使用新的XCTestExpectation,它是为测试异步进程而设计的。

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

  2. 您显然在做一些不工作的事情,因为您在后台队列上运行它。典型问题包括

    • 如果您尝试从后台线程运行该计时器,那将无法正常工作,除非您使用了标准计时器解决方法之一(将其安排在主运行循环中,将计时器的创建分派回主线程,使用调度计时器等)。

      前面已经详细描述了这些替代方案,事实证明您是从 viewWillAppear 启动计时器,所以这都是学术性的。

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

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

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

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

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

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

【讨论】:

  • 我无法使用测试期望,因为我正在测试经过身份验证的 Web 服务调用。测试必须等待执行,直到身份验证请求完成。而且每次测试都需要认证,所以我在设置阶段执行认证请求
  • 这正是期望所做的。它保持测试直到所有异步任务完成(或超时)。
  • 另外,计时器正在视图控制器中的viewWillAppear 中执行
  • 除了我如何测试异步任务之外,它并没有解释为什么这个异步请求会在应用程序的一个完全分离的方面破坏计时器
  • @DanF,绝大多数 UIKit 都不是线程安全的。从后台线程调用 UIKit 方法会导致很多问题。不要这样做。正如其他人指出的那样,让您的完成块在主队列上运行很容易。 (这就是我的建议)。如果您决定让完成处理程序在后台队列上运行,那么您需要将您的 performSelector 调用更改为 performSelectorOnMainThread:withObject:waitUntilDone:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-31
相关资源
最近更新 更多