objective c - finished - Solicitud asincrónica al servidor desde el hilo de fondo
nsurlconnection error codes (3)
Las solicitudes NSURL son completamente asincrónicas de todos modos. Si necesita hacer una solicitud NSURL desde un hilo que no sea el hilo principal, creo que la mejor manera de hacerlo es simplemente hacer la NSURLRequest
desde el hilo principal .
// Code running on _not the main thread_:
[self performSelectorOnMainThread:@selector( SomeSelectorThatMakesNSURLRequest )
withObject:nil
waitUntilDone:FALSE] ; // DON''T block this thread until the selector completes.
Todo lo que hace es disparar la solicitud HTTP desde el hilo principal (para que realmente funcione y no desaparezca misteriosamente). La respuesta HTTP volverá a las devoluciones de llamada como de costumbre.
Si quieres hacer esto con GCD, simplemente puedes ir
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
// Perform your HTTP request (this runs on the main thread)
} ) ;
MAIN_QUEUE
ejecuta en el hilo principal.
Así que la primera línea de mi función HTTP get se ve así:
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
}
Tengo el problema cuando intenté hacer solicitudes asíncronas al servidor desde el hilo de fondo. Nunca obtuve resultados de esas solicitudes. Ejemplo simple que muestra el problema:
@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
Entonces llamo esto desde el hilo principal
asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self;
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
método downloadImage se enumera a continuación:
-(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];
}
El problema es que el método imageDownloadDidFinish nunca se llama. Además ninguno de los métodos
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
son llamados. Sin embargo si reemplazo
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
por
[self performSelector:@selector(downloadImage) withObject:nil];
todo funciona correctamente Supongo que el hilo de fondo muere antes de que la solicitud asíncrona haya terminado su trabajo y esto causa el problema, pero no estoy seguro. ¿Estoy en lo correcto con estas suposiciones? ¿Hay alguna forma de evitar este problema?
Sé que puedo usar la solicitud de sincronización para evitar este problema, pero es un ejemplo simple, la situación real es más compleja.
Gracias por adelantado.
Puede iniciar la conexión en un hilo de fondo, pero debe asegurarse de que los métodos de delegado se invoquen en el hilo principal. Esto no se puede hacer con
[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
ya que comienza de inmediato.
Haga esto para configurar la cola de delegados y funciona incluso en subprocesos secundarios:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
Sí, el hilo está saliendo. Puedes ver esto agregando:
-(void)threadDone:(NSNotification*)arg
{
NSLog(@"Thread exiting");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadDone:)
name:NSThreadWillExitNotification
object:nil];
Puedes evitar que el hilo salga con:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[self downloadImage:urlString];
CFRunLoopRun(); // Avoid thread exiting
[pool release];
}
Sin embargo, esto significa que el hilo nunca saldrá. Entonces debes detenerlo cuando termines.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
Obtenga más información sobre Run Loops en la Guía Threading y la Referencia RunLoop .