2012-04-20 16 views
36

Sto utilizzando AFNetworking e SDURLCache per tutte le operazioni di rete.SDURLCache con AFNetworking e modalità offline non funzionante

ho SDURLCache insieme in questo modo:

SDURLCache *urlCache = [[SDURLCache alloc] 
     initWithMemoryCapacity:1024*1024*2 // 2MB mem cache 
     diskCapacity:1024*1024*15 // 15MB disk cache 
     diskPath:[SDURLCache defaultCachePath]]; 
    [urlCache setMinCacheInterval:1]; 
    [NSURLCache setSharedURLCache:urlCache]; 

Tutta la mia richiesta utilizza cachePolicy NSURLRequestUseProtocolCachePolicy, che secondo la documentazione di Apple funziona così:

Se un NSCachedURLResponse non esiste per la richiesta , quindi i dati vengono recuperati dalla fonte di origine. Se esiste una risposta memorizzata nella cache per la richiesta, il sistema di caricamento degli URL controlla la risposta per determinare se specifica che il contenuto deve essere riconvalidato. Se il contenuto deve essere riconvalidato, viene effettuata una connessione alla sorgente di origine per vedere se è stata modificata. Se non è stato modificato, , la risposta viene restituita dalla cache locale. Se è stato modificato, i dati vengono recuperati dalla fonte di origine.

Se la risposta memorizzata nella cache non specifica che il contenuto deve essere riconvalidato, viene esaminata l'età massima o la scadenza specificata nella risposta . Se la risposta memorizzata nella cache è abbastanza recente, la risposta viene restituita dalla cache locale. Se la risposta è determinata per essere aggiornata, la fonte di origine viene verificata per i nuovi dati . Se sono disponibili dati più recenti, i dati vengono recuperati dall'origine di origine , altrimenti viene restituito dalla cache.

Quindi tutto funziona perfettamente anche in modalità aereo purché la cache non sia obsoleta. Quando la cache scade (max-age e altri), viene chiamato il blocco degli errori.

ho scavato un po 'all'interno della SDURLCache e questo metodo restituisce una risposta con dati validi (ho analizzato i dati in una stringa e contiene le informazioni memorizzate nella cache)

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { 
    request = [SDURLCache canonicalRequestForRequest:request]; 

    NSCachedURLResponse *memoryResponse = 
     [super cachedResponseForRequest:request]; 
    if (memoryResponse) { 
     return memoryResponse; 
    } 

    NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL]; 

    // NOTE: We don't handle expiration here as even staled cache data is 
    // necessary for NSURLConnection to handle cache revalidation. 
    // Staled cache data is also needed for cachePolicies which force the 
    // use of the cache. 
    __block NSCachedURLResponse *response = nil; 
    dispatch_sync(get_disk_cache_queue(), ^{ 
     NSMutableDictionary *accesses = [self.diskCacheInfo 
      objectForKey:kAFURLCacheInfoAccessesKey]; 
     // OPTI: Check for cache-hit in in-memory dictionary before to hit FS 
     if ([accesses objectForKey:cacheKey]) { 
      response = [NSKeyedUnarchiver unarchiveObjectWithFile: 
       [_diskCachePath stringByAppendingPathComponent:cacheKey]]; 
      if (response) { 
       // OPTI: Log entry last access time for LRU cache eviction 
       // algorithm but don't save the dictionary 
       // on disk now in order to save IO and time 
       [accesses setObject:[NSDate date] forKey:cacheKey]; 
       _diskCacheInfoDirty = YES; 
      } 
     } 
    }); 

    // OPTI: Store the response to memory cache for potential future requests 
    if (response) { 
     [super storeCachedResponse:response forRequest:request]; 
    } 

    return response; 
} 

Quindi a questo punto ho idea di cosa fare, perché credo che la risposta è gestito dal sistema operativo e poi AFNetworking riceve una

- (void)connection:(NSURLConnection *)__unused connection 
    didFailWithError:(NSError *)error 

all'interno AFURLConnectionOperation.

+0

Sto affrontando esattamente lo stesso problema in questo momento. Hai trovato una soluzione? – mkto

+0

No, ho fatto una soluzione pazzesca, niente di cui essere orgoglioso :-( – Ecarrion

+0

ho inviato una mail all'autore Pete che ha forgiato SDURLCache spero che abbia una risposta .. – mkto

risposta

10

Bene, ho finalmente raggiunto un non così brutta soluzione:

Prima

Se stai usando IOS5/IOS6 si può cadere SDURLCache e utilizzare il nativo uno:

//Set Cache 
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 
                diskCapacity:20 * 1024 * 1024 
                 diskPath:nil]; 
[NSURLCache setSharedURLCache:URLCache]; 

Ma ricorda che in IOS5 le richieste https non verranno memorizzate nella cache in IOS6 lo faranno.

Seconda

Abbiamo bisogno di aggiungere i seguenti quadri al nostro Prefix.pch così AFNetworking può iniziare a monitorare la nostra connessione internet.

#import <MobileCoreServices/MobileCoreServices.h> 
#import <SystemConfiguration/SystemConfiguration.h> 

Terzo

Abbiamo bisogno e l'istanza AFHTTPClient in modo che possiamo intercettare ogni richiesta in uscita e cambiare la sua cachePolicy

-(NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters { 

    NSMutableURLRequest * request = [super requestWithMethod:method path:path parameters:parameters]; 
    if (request.cachePolicy == NSURLRequestUseProtocolCachePolicy && self.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) { 
     request.cachePolicy = NSURLRequestReturnCacheDataDontLoad; 
    } 

    if (self.networkReachabilityStatus == AFNetworkReachabilityStatusUnknown) { 

     puts("uknown reachability status"); 
    } 

    return request; 
} 

Con queste paci di codice possiamo ora rilevare quando il wifi/3g non è disponibile e specifica la richiesta di utilizzare sempre la cache, a prescindere da cosa. (Modalità offline)

Note

  • Io ancora non so cosa fare quando il networkReachabilityStatus è AFNetworkReachabilityStatusUnknown Ciò può accadere è una richiesta viene effettuata non appena si avvia l'applicazione e AF non ha ottenuto lo stato di internet ancora.

  • Ricordare che per fare in modo che funzioni, il server deve impostare le intestazioni di cache corrette nella risposta http.

UPDATE

Sembra IOS6 ha alcuni problemi di caricamento risposte memorizzate nella cache in situazioni di non-internet, quindi, anche se la richiesta viene memorizzato nella cache e il criterio di cache richiesta viene seted al NSURLRequestReturnCacheDataDontLoad la richiesta avrà esito negativo.

Quindi una brutta soluzione è modificare (void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)error in AFURLConnectionOperation.m per recuperare la risposta memorizzata nella cache se la richiesta non riesce ma solo per criteri di cache specifici.

- (void)connection:(NSURLConnection __unused *)connection 
    didFailWithError:(NSError *)error 
{ 
    self.error = error; 

    [self.outputStream close]; 

    [self finish]; 

    self.connection = nil; 

    //Ugly hack for making the request succeed if we can find a valid non-empty cached request 
    //This is because IOS6 is not handling cache responses right when we are in a no-connection sittuation 
    //Only use this code for cache policies that are supposed to listen to cache regarding it's expiration date 
    if (self.request.cachePolicy == NSURLRequestUseProtocolCachePolicy || 
     self.request.cachePolicy == NSURLRequestReturnCacheDataElseLoad || 
     self.request.cachePolicy == NSURLRequestReturnCacheDataDontLoad) { 

     NSCachedURLResponse * cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]; 
     if (cachedResponse.data.length > 0) { 
      self.responseData = cachedResponse.data; 
      self.response = cachedResponse.response; 
      self.error = nil; 
     } 
    } 
} 
+1

grazie per questa risposta, un riassunto davvero utile. Penso che sia necessario modificare alcune parti del tuo ultimo esempio di codice: le tue aggiunte dovrebbero essere applicate prima di chiamare '[self finished]', altrimenti il ​​blocco 'failure' potrebbe essere chiamato. Ho anche sperimentato una condizione di gara che ha reso davvero difficile il debug. –

+1

Inoltre dovrei dire che alla fine ho finito con l'uso di SDURLCache. NSURLCache non ha restituito una risposta memorizzata nella cache su iOS 6 in modo affidabile in modalità offline. Che dolore. –

+2

Ok, ho notizie su questo direttamente da Apple. Ho parlato con un ingegnere Apple su un Tech Talk di iOS. * Me: * "... Mi piacerebbe usare NSURLCache, ora che supporta la cache del disco ...." * Ragazzo di Apple: * "No, non lo faresti!" Ha poi spiegato che non è stato creato per scenari offline espliciti e che è stato progettato per accelerare Safari e non dovrebbe essere utilizzato per i download manuali. - Beh, perché non l'hanno scritto nei documenti? Mi avrebbe risparmiato un bel po 'di ore di vita. –

0

Impossibile dire molto senza le intestazioni HTTP - ma il motivo più comune per questo è NSURLProtocol forzare la riconvalida prima di consegnare la risposta memorizzata nella cache a WebView.

prega di dare un'occhiata qui: http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588

+0

Alcuni buoni consigli, con molti avvisi (dall'autore) – JOM

0

Suona come si desidera la richiesta per avere successo, anche se la cache dice i dati è scaduto e deve essere recuperato dal server. Potresti avere un po 'di fortuna nell'impostare la politica cache (politica diversa per online o offline) di alcune richieste in cui preferiresti utilizzare dati obsoleti che non riuscire.

NSMutableURLRequest -> setCachePolicy

Assomiglia NSURLRequestReturnCacheDataDontLoad è il criterio che si desidera per la modalità non in linea.

Spero che questo aiuti!

+0

Ma ciò implica che devo verificare la connessione a Internet ogni volta che eseguo una richiesta e cambio di conseguenza la politica cache, giusto? – Ecarrion

+0

Cerca in setReachabilityStatusChangeBlock in AFHTTPClient per monitorare le modifiche di rete. Forse potresti cambiare la politica della cache lì? Tuttavia, dovresti provare ad impostare il criterio di cache come ho notato sopra e vedere se ti dà il comportamento "offline mode" desiderato. Se tu, puoi esaminare il tuo meccanismo di commutazione tra online/offline. – Dave

+1

Quindi, questo funziona? – Renetik

Problemi correlati