当我尝试从后台线程向服务器发出异步请求时,我遇到了问题。我从来没有得到过这些要求的结果。显示问题的简单示例:
@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];
一切正常。我假设后台线程在异步请求完成它之前死亡,这导致了问题,但我不确定。我对这个假设是对的吗?有什么方法可以避免这个问题吗?
我知道我可以使用同步请求来避免这个问题,但这只是一个简单的例子,真实情况更复杂。
提前致谢。
答案 0 :(得分: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());
}
中了解有关运行循环的详情
答案 1 :(得分:1)
您可以在后台线程上启动连接,但必须确保在主线程上调用委托方法。
无法做到这一点[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
因为它立即开始。
执行此操作以配置委托队列,它甚至可以在辅助线程上运行:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
答案 2 :(得分: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
}