我已经为我的 iOS 应用程序编写了自己的 HTTPClient 实现,以异步下载指定 URL 的内容。HTTPClient 使用 NSOperationQueue 将 NSURLConnection 请求排入队列。我选择 NSOperationQueue 是因为我想在任何时候取消任何或所有正在进行的 NSURLConnection。
我对如何实现我的 HTTPClient 进行了大量研究,并且我有两种执行 NSURLConnection 的选择:
1) 在单独的辅助线程上执行每个入队的 NSURLConnection。NSOperationQueue 在后台的辅助线程上执行每个入队操作,因此我不需要明确地执行任何操作来生成辅助线程,除了在 NSOperation 子类的重写启动方法中启动我的 NSURLConnection 并为生成的辅助线程运行运行循环直到 connectionDidFinishLoading 或connectionDidFailWithError 被调用。如下所示:
if (self.connection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
} while (!self.isFinished);
}
2) 在主线程上执行每个入队的 NSURLConnection。为此,在 start 方法中,我使用 performSelectorOnMainThread 并在主线程上再次调用 start 方法。使用这种方法,我使用 NSRunLoopCommonModes 调度 NSURLConnection,如下所示:
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
我选择了第二种方法并实施了它。根据我的研究,第二种方法似乎更好,因为它不会为每个 NSURLConnection 启动单独的辅助线程。现在在任何时间点,在应用程序中可能有许多请求同时进行,并且使用第一种方法,这意味着将产生相同数量的辅助线程,并且在相关联的 url 请求完成之前不会返回到池中。
我的印象是我仍然通过使用 NSRunLoopCommonModes 调度 NSURLConnection 与第二种方法同时运行。换句话说,我认为我使用 NSRunLoopCommonModes 而不是多线程来实现并发性,以便 NSURLConnection 的观察者会尽快调用 connectionDidFinishLaunching 或 connectionDidFailWithError ,而不管主线程在那个时候对 UI 做了什么时间。
不幸的是,当今天早上我的一位同事向我展示当前的实现时,我的所有理解都被证明是错误的,直到其中一个视图控制器上的滚动视图停止滚动时,NSURLConnection 才会返回。获取数据的 NSURLRequest 在滚动视图即将停止滚动时启动,但即使它在滚动视图停止调用之前完成,不知何故 NSURLConnection 不会回调 connectionDidFinishLoading 或 connectionDidFailWithError 直到滚动视图完全停止滚动。这意味着在主线程上使用 NSRunLoopCommonModes 调度 NSURLConnection 以获得与 UI 操作(触摸/滚动)的真正并发的整个想法被证明是错误的,并且 NSURLConnection 仍然等到主线程忙于滚动滚动视图。
我尝试切换到使用辅助线程的第一种方法,它就像一个魅力。当滚动视图仍在滚动时,NSURLConnection 仍然调用它的协议方法之一。这很清楚,因为现在 NSURLConnection 没有在主线程上运行,所以它不会等待滚动视图停止滚动。
我真的不想使用第一种方法,因为多线程导致它很昂贵。
如果我对第二种方法的理解不正确,有人可以告诉我吗?如果正确,使用 NSRunLoopCommonModes 调度 NSURLConnection 不能按预期工作的原因可能是什么?
如果答案更具描述性,我将不胜感激,因为它应该为我清除更多关于 NSRunLoop 和 NSRunLoopModes 如何工作的疑虑。只是为了说明我已经阅读了很多次的文档。