2012-02-23 9 views
7

Ho bisogno di fare alcune chiamate POST al mio server, ma non ho bisogno di bloccare il thread principale. Come ho capito, NSMutableURLRequest e NSURLConnection non sono thread-safe, quindi è meglio usare il metodo asincrono di NSURLConnection.Objective-C: Async/Background POST senza utilizzare il metodo delegate?

La mia domanda su questo è, come posso impacchettare bene in un metodo, invece di dover utilizzare il metodo delegato? Io preferirei fare:

NSData *returnedData = [Utility postDataToURL:@"some string of data"]; 

Questo è come è facile fatto con il seguente metodo:

[NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError]; 

E 'così bello tenere tutto all'interno di un metodo, quindi basta avere i miei dati restituiti da esso !

Esistono metodi basati su blocchi per questo? Diventa un problema quando ho bisogno di scrivere metodi per circa 50 chiamate diverse e ognuno deve utilizzare lo stesso metodo delegato. Sto andando su questo nel modo sbagliato?

Questo deve essere solo per iOS5.

+1

Tu dici che non vuoi blocca il thread principale, ma quello che hai detto che vuoi fare è molto chiaramente sincrono, cioè bloccherà il thread principale (o almeno, il thread da cui lo stai chiamando). –

+0

Questo è un esempio di quanto sia facile riuscire a restituire i dati. –

risposta

9

iOS 5 aggiunge sendAsynchronousRequest:queue:completionHandler: che fa ciò che penso che si desidera. Ho impostato il mio codice per usarlo, se disponibile, ma per tornare a eseguire un recupero sincrono su una coda GCD in background e saltare sul thread principale con il risultato se non lo è. Quest'ultimo sarà meno efficiente dal punto di vista energetico ma è solo per mantenere il supporto legacy.

if([NSURLConnection respondsToSelector:@selector(sendAsynchronousRequest:queue:completionHandler:)]) 
{ 
    // we can use the iOS 5 path, so issue the asynchronous request 
    // and then just do whatever we want to do 
    [NSURLConnection sendAsynchronousRequest:request 
     queue:[NSOperationQueue mainQueue] 
     completionHandler: 
     ^(NSURLResponse *response, NSData *data, NSError *error) 
     { 
      [self didLoadData:data]; 
     }]; 
} 
else 
{ 
    // fine, we'll have to do a power inefficient iOS 4 implementation; 
    // hop onto the global dispatch queue... 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
    ^{ 
     // ... perform a blocking, synchronous URL retrieval ... 
     NSError *error = nil; 
     NSURLResponse *urlResponse = nil; 
     NSData *responseData = 
      [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error]; 

     // ... and hop back onto the main queue to handle the result 
     dispatch_async(dispatch_get_main_queue(), 
     ^{ 
      [self didLoadData:responseData]; 
     }); 
    }); 
} 

Nel codice di produzione si sarebbe in realtà controllare le error s ei codici di risposta HTTP (come una risposta del server 404 è probabilmente altrettanto un errore dal punto di vista come un errore di connessione), ovviamente.

+0

Sto creando un'applicazione iOS 5, quindi funziona perfettamente! Grazie! –

+0

È meglio impostare la coda su [NSOperationQueue mainQueue] piuttosto che sul mio uso personale con nome? –

+0

Dipende da te davvero. Ho usato 'mainQueue' perché eseguo alcune cose UIKit nel gestore di completamento: la coda che stai identificando è dove vuoi che si verifichi il gestore di completamento, quindi se vuoi che ciò avvenga in background, passa nella tua coda alternativa. – Tommy

1

iOS 5.0 > è possibile utilizzare il metodo sendAsynchronousRequest al numero NSURLConnection Class e utilizza i blocchi. Se si desidera supportare anche iOS 4.0 >, è necessario scrivere uno dei propri blocchi di caricamento degli URL asincroni che è abbastanza facile da scrivere. Stai meglio usando MKNetworkKit.

ma non è necessario bloccare il thread principale. Come ho capito, NSMutableURLRequest e NSURLConnection non sono thread-safe, quindi è meglio usare il metodo async di NSURLConnection.

Non si desidera eseguire la connessione di rete sincrona blocca il thread da cui viene chiamato (è ancora peggio se il thread principale). Puoi fare una connessione di rete asincrona sul thread principale. Se si desidera chiamare NSURLConnection sul thread non principale, è necessario creare un RunLoop su quel thread (se non lo si fa, i metodi delegate di NSURLConnection non vengono mai chiamati).

+1

Sebbene il resto sia sostanzialmente uguale alla mia risposta, non sono sicuro di essere d'accordo con la frase finale; questo è qualcosa che puoi implementare per 5 o 4 in circa 50 righe di codice, quindi incorporare una grande libreria di terze parti non sembra che renderebbe il poster migliore, soprattutto considerando il supporto e le ramificazioni del test. – Tommy

+0

@Tommy Per questo specifico compito, sono d'accordo sul fatto che non è necessaria una libreria di terze parti. Motivo che stavo raccomandando di usare che sta andando avanti se vuoi aggiungere più funzionalità alla tua classe di Utility come un modo semplice per gestire i parametri REST, la cache delle immagini, ecc. Sebbene tu possa continuare ad aggiungere funzionalità in base alle tue esigenze, il tuo design può diventare complicato e questi framework di terze parti sono a portata di mano. – 0x8badf00d

0

Uso un metodo di facciata per op-queue un operatore interno che emette la chiamata sincrona. A seconda della velocità con cui si inviano le chiamate, potrebbe funzionare. Esempio:

// Presented in @interface 
-(void)sendPostRequest { 
    // Last chance to update main thread/UI 
    NSInvocationOperation *op = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sendPostRequest_internal) object:nil] autorelease]; 
    [opQueue addOperation:op]; 
} 

// Hidden in @implementation 
-(void)sendPostRequest_internal { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    NSURLRequest *request = // yadda, you might use NSURLMutableRequest 
    NSURLResponse *response = nil; 
    NSError *error = nil; 
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error]; 

    // process data, retain things as needed, post results using performSelectorOnMainThread: 

    [pool release]; 
} 

Funziona abbastanza bene per i miei scopi, ma potrebbe essere necessario scavare un po 'più in profondità alla roba asincrono, che non è davvero troppo male.

1

Ho avuto questo problema pre-5.0 così ho fatto un po 'di classe per gestire il protocollo delegato NSURLConnection e di offrire ai chiamanti un'interfaccia con chiusure:

BetterNSURLConnection.h

@property (retain, nonatomic) NSURLRequest *request; 

BetterNSURLConnection.m

@property (retain, nonatomic) NSURLConnection *connection; 
@property (retain, nonatomic) NSHTTPURLResponse *response; 
@property (retain, nonatomic) NSMutableData *responseData; 

@property (copy, nonatomic) void (^completionBlock)(id, NSHTTPURLResponse *); 
@property (copy, nonatomic) void (^errorBlock)(NSError *); 

... È possibile aggiungere typedef per fare quelle firme a blocchi più bella ... poi:

@synthesize connection = _connection; 
@synthesize response = _response; 
@synthesize responseData = _responseData; 
@synthesize completionBlock = _completionBlock; 
@synthesize errorBlock = _errorBlock; 
@synthesize request=_request; 


- (void)startWithCompletion:(void (^)(id, NSHTTPURLResponse *))completionBlock error:(void (^)(NSError *))errorBlock { 

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 

    self.completionBlock = completionBlock; 
    self.errorBlock = errorBlock; 
    self.responseData = [NSMutableData data]; 

    NSURLConnection *connection = [NSURLConnection connectionWithRequest:self.request delegate:self]; 
    self.connection = connection; 
    [self.connection start]; 
    [connection release]; 
} 

... poi fare il delegato così:

- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)response { 

    [self.responseData setLength:0]; 
    self.response = response; 
} 

- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data { 

    [self.responseData appendData:data]; 
} 

- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error { 

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 
    self.errorBlock(error); 
    self.connection = nil; 
} 

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { 

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 

    if (self.response.statusCode >= 400) { 
     self.errorBlock(error); 
    } else { 
     // i do json requests, and call a json parser here, but you might want to do something different 
     id result = [self parseResponse:self.responseData]; 
     self.completionBlock(result, self.response); 
    } 
    self.connection = nil; 
} 
Problemi correlati