2009-11-13 46 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];