从后台线程到服务器的asynchronous请求
当我试图从后台线程对服务器执行asynchronous请求时,我遇到了这个问题。 我从来没有得到这些要求的结果。 显示问题的简单例子:
@protocol AsyncImgRequestDelegate -(void) imageDownloadDidFinish:(UIImage*) img; @end @interface AsyncImgRequest : NSObject { NSMutableData* receivedData; id<AsyncImgRequestDelegate> delegate; } @property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate; -(void) downloadImage:(NSString*) url ; @end @implementation AsyncImgRequest -(void) downloadImage:(NSString*) url { NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; if (theConnection) { receivedData=[[NSMutableData data] retain]; } else { } } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]]; [connection release]; [receivedData release]; } @end
然后我从主线程调用这个
asyncImgRequest = [[AsyncImgRequest alloc] init]; asyncImgRequest.delegate = self; [self performSelectorInBackground:@selector(downloadImage) withObject:nil];
方法downloadImage列出如下:
-(void) downloadImage { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [asyncImgRequest downloadImage:@"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"]; [pool release]; }
问题是,方法imageDownloadDidFinish永远不会被调用。 而且没有任何方法
- (void)connectionDidFinishLoading:(NSURLConnection *)connection - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
叫做。 但是,如果我更换
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
通过
[self performSelector:@selector(downloadImage) withObject:nil];
一切工作正确。 我假设后台线程在asynchronous请求完成之前死了它的工作,这导致了这个问题,但我不知道。 我对这个假设是否正确? 有什么办法可以避免这个问题?
我知道我可以使用同步请求来避免这个问题,但这只是一个简单的例子,实际情况更复杂。
提前致谢。
是的,线程正在退出。 你可以通过添加:
-(void)threadDone:(NSNotification*)arg { NSLog(@"Thread exiting"); } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadDone:) name:NSThreadWillExitNotification object:nil];
你可以保持线程退出:
-(void) downloadImage { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; [self downloadImage:urlString]; CFRunLoopRun(); // Avoid thread exiting [pool release]; }
但是,这意味着线程将永远不会退出。 所以当你完成的时候你需要停下来。
- (void)connectionDidFinishLoading:(NSURLConnection *)connection { CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { CFRunLoopStop(CFRunLoopGetCurrent()); }
详细了解线程指南和RunLoop参考中的运行循环。
您可以在后台线程上启动连接,但必须确保在主线程上调用委托方法。 这是无法完成的
[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
因为它立即开始。
这样做来configuration委托队列,甚至在辅助线程上也可以工作:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO]; [connection setDelegateQueue:[NSOperationQueue mainQueue]]; [connection start];
无论如何NSURLRequests是完全asynchronous的。 如果你需要从主线程以外的线程创build一个NSURLRequest,我认为最好的办法就是从主线程 创buildNSURLRequest
。
// Code running on _not the main thread_: [self performSelectorOnMainThread:@selector( SomeSelectorThatMakesNSURLRequest ) withObject:nil waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.
所有这一切都是从主线程的HTTP请求(这样它实际上工作,并不神秘消失)。 HTTP响应将像往常一样回到callback中。
如果你想用GCD做这个,你可以走了
// From NOT the main thread: dispatch_async( dispatch_get_main_queue(), ^{ // // Perform your HTTP request (this runs on the main thread) } ) ;
MAIN_QUEUE
在主线程上运行。
所以我的HTTP获取函数的第一行如下所示:
void Server::get( string queryString, function<void (char*resp, int len) > onSuccess, function<void (char*resp, int len) > onFail ) { if( ![NSThread isMainThread] ) { warning( "You are issuing an HTTP request on NOT the main thread. " "This is a problem because if your thread exits too early, " "I will be terminated and my delegates won't run" ) ; // From NOT the main thread: dispatch_async( dispatch_get_main_queue(), ^{ // Perform your HTTP request (this runs on the main thread) get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request, // but on the main thread. } ) ; return ; } // proceed with HTTP request normally }