Objective c NSURLConnection与nsrunlopCommonModes的连接
我已经为我的iOS应用程序编写了自己的HTTPClient实现,以异步下载指定URL的内容。HTTPClient使用NSOperationQueue将NSURLConnection请求排队。我选择NSOperationQueue是因为我想在任何时间点取消任何或所有正在进行的NSURLConnection 我对如何实现我的HTTPClient做了大量研究,对于执行NSURLConnection,我有两种选择: 1) 在单独的辅助线程上执行每个排队的NSURLConnection。NSOperationQueue在后台的次线程上执行每个排队操作,因此我不需要显式地执行任何操作来生成次线程,除了在NSOperation子类的重写的start方法中启动NSURLConnection并为生成的次线程运行runloop,直到ConnectionIDFinishLoading或调用connectionIDFailWitherRor。如下所示:Objective c NSURLConnection与nsrunlopCommonModes的连接,objective-c,ios,concurrency,nsurlconnection,nsrunloop,Objective C,Ios,Concurrency,Nsurlconnection,Nsrunloop,我已经为我的iOS应用程序编写了自己的HTTPClient实现,以异步下载指定URL的内容。HTTPClient使用NSOperationQueue将NSURLConnection请求排队。我选择NSOperationQueue是因为我想在任何时间点取消任何或所有正在进行的NSURLConnection 我对如何实现我的HTTPClient做了大量研究,对于执行NSURLConnection,我有两种选择: 1) 在单独的辅助线程上执行每个排队的NSURLConnection。NSOperati
if (self.connection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
} while (!self.isFinished);
}
NSRunLoop *runloop; //global
self.connection = [[NSURLConnection alloc] initWithRequest:self.urlRequest delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:runloop forMode:NSRunLoopCommonModes];
[self.connection start];
2) 在主线程上执行每个排队的NSURLConnection。为此,在start方法中,我使用performSelectorOnMainThread并在主线程上再次调用start方法。使用此方法,我将NSURLConnection与nsrunlopCommonModes的时间安排如下:
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
我选择了第二种方法并实现了它。根据我的研究,第二种方法似乎更好,因为它不会为每个NSURL连接启动单独的辅助线程。现在,在任何时候,应用程序中都可能有许多请求同时进行,使用第一种方法,这意味着将产生相同数量的辅助线程,并且在相关url请求完成之前不会返回池
我的印象是,我仍然在通过调度NSURLConnection和nsrunlopCommonModes与第二种方法并行运行。换句话说,对于这种方法,我认为我使用的是nsrunlopCommonModes,而不是多线程并发,因此NSURLConnection的观察者将尽快调用connectionIDFinishLaunching或connectionIDFailWitherRor,而不管主线程当时对UI做了什么
不幸的是,今天早上我的一位同事告诉我,在当前的实现中,NSURLConnection只有在其中一个视图控制器上的滚动视图停止滚动时才会返回,这证明了我所有的理解都是错误的。当滚动视图即将停止滚动时,NSURLRequest获取数据会启动,但即使在滚动视图停止调用之前完成,NSURLConnection也不会在滚动视图完全停止滚动之前回调ConnectionIDFinishLoading或ConnectionIDFailWitherRor。这意味着在主线程上使用nsrunlopCommonModes调度NSURLConnection以获得与UI操作(触摸/滚动)的真正并发性的整个想法被证明是错误的,NSURLConnection仍在等待,直到主线程忙于滚动滚动滚动视图
我试着切换到使用辅助线程的第一种方法,效果非常好。当滚动视图仍在滚动时,NSURLConnection仍调用其协议方法之一。这是清楚的,因为现在NSURLConnection没有在主线程上运行,所以它不会等待滚动视图停止滚动
我真的不想使用第一种方法,因为它由于多线程而很昂贵
如果我对第二种方法的理解不正确,有人能告诉我吗?如果正确,那么使用nsrunlopCommonModes调度NSURLConnection的原因可能是什么
如果答案更具描述性,我将不胜感激,因为它可以为我消除更多关于nsrunlop和NSRunLoopModes工作方式的疑问。我已经多次阅读了这方面的文档。调度运行循环源不允许源的回调与其他源的回调同时运行 在网络通信的情况下,内核处理的事情,如接收和缓冲数据包,无论应用程序做什么,都会同时发生。然后,内核将套接字标记为可读或可写的,例如,如果线程在这样的调用中被阻塞,则可能会唤醒
select()
或kevent()
调用。如果线程正在执行其他操作,比如处理滚动事件,那么在执行返回到运行循环之前,它不会注意到套接字的可读写性。只有这样,NSURLConnection
的运行循环源才会调用其回调,让NSURLConnection处理套接字状态更改,并可能调用您的委托方法
下一个问题是,当一个运行循环有多个源并且有多个源准备就绪时会发生什么。例如,事件队列中有更多的滚动事件,并且套接字是可读或可写的。理想情况下,您可能需要一个公平的算法来服务运行循环源。实际上,GUI事件的优先级可能高于其他运行循环源。此外,运行循环源相对于其他源具有固有的优先级(“顺序”)
通常,即时维护NSURLConnection
并不重要。通常允许它等待主线程的运行循环到达它是可以的。考虑到,同样的原因,<代码> NSURLCONNECT/<代码>的运行循环源不会在滚动时被服务,但是在后台线程上处理它不会有用户可见的效果。例如,它将如何影响应用程序的UI?它将使用-performSelectorOnMainThread:…
或类似的方法来安排更新。但这和NSURLConnection
run循环源代码一样可能会被饿死
然而,如果你绝对不能忍受这种可能的延迟,在sc和sc之间有一个中间地带
self.connection = [[NSURLConnection alloc] initWithRequest:self.urlRequest
delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
NSRunLoop *runloop; //global
self.connection = [[NSURLConnection alloc] initWithRequest:self.urlRequest delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:runloop forMode:NSRunLoopCommonModes];
[self.connection start];
runloop = [NSRunLoop currentRunLoop];
while (!finished)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
static NSThread *connectionProcessingThread;
static NSTimer *keepRunloopBusy;
static NSRunLoop *oauth2runLoop;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
connectionProcessingThread = [[NSThread alloc] initWithBlock:^{
oauth2runLoop = [NSRunLoop currentRunLoop];
keepRunloopBusy = [NSTimer timerWithTimeInterval:DBL_MAX repeats:YES block:^(NSTimer* timer) {
NSLog(@"runloop is kept busy with this keepalive work");
}];
[oauth2runLoop addTimer:keepRunloopBusy forMode:NSRunLoopCommonModes];
[oauth2runLoop run];
}];
[connectionProcessingThread start];
atomic_thread_fence(memory_order_release);
});
}
NSURLConnection *aConnection = [[NSURLConnection alloc] initWithRequest:startRequest delegate:self startImmediately:NO]; // don't start yet
if( [NSRunLoop currentRunLoop] != [NSRunLoop mainRunLoop]) {
atomic_thread_fence(memory_order_acquire);
[aConnection scheduleInRunLoop:oauth2runLoop forMode:NSRunLoopCommonModes];
} else {
[aConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; // let's first schedule it in the main runloop.
}
[aConnection start]; // now start