2009-11-13 111 views
37

我試圖從後臺線程向服務器做異步請求時遇到了問題。我從來沒有得到這些要求的結果。這說明了什麼問題簡單的例子:從後臺線程到服務器的異步請求

@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]; 

一切工作正確。我假設後臺線程在異步請求完成之前就已經死了,這導致了問題,但我不確定。我對這個假設是否正確?有什麼辦法可以避免這個問題嗎?

我知道我可以使用同步請求來避免這個問題,但它只是一個簡單的例子,實際情況更復雜。

在此先感謝。

回答

59

是的,線程正在退出。您可以通過添加看到:

-(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()); 
} 

瞭解在Threading GuideRunLoop Reference更多的運行循環。

+0

研究關於如何在NSInvocationOperation內執行一個異步NSURLConnection的幾個小時(8個)之後,沒有一個解決方案是像CFRunLoopRun()和CFRunLoopStop()一樣優雅。這比使用KVO協議執行「isExecuting」實例變量要好得多。有一點需要注意的是,我使用@selector(performSelectorOnMainThread:withObject:waitUntilDone:modes :)作爲模式傳遞[NSArray arrayWithObject:NSDefaultRunLoopMode]。主線程滿足UIKit的要求,NSDefaultRunLoopMode保持UI交互的順暢。非常感謝! – 2009-12-19 10:40:49

+0

希望我能投票更多。這非常有幫助。 – 2012-01-24 17:37:09

+6

對於使用ARC編譯的項目,「NSAutoReleasePool」不可用,所以代碼看起來像「@autoreleasepool {[self downloadImage:urlString]; CFRunLoopRun(); }' – alokoko 2012-03-06 21:12:54

0

無論如何NSURLRequests是完全異步的。如果你需要從比主線程的線程上做出的NSURLRequest,我認爲要做到這一點的最好辦法就是從主線程使NSURLRequest

// 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響應將像往常一樣回到回調中。

如果你想與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 GET函數的第一行是這樣的:

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 
} 
1

您可以開始在後臺線程的連接,但你必須確保委託方法被調用的主線程。這不能與

[[NSURLConnection alloc] initWithRequest:urlRequest 
           delegate:self]; 

因爲它立即開始。

這樣做是爲了配置代理隊列和它的作品甚至在二級螺紋:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest 
                   delegate:self 
                 startImmediately:NO]; 
[connection setDelegateQueue:[NSOperationQueue mainQueue]]; 
[connection start];