37 votes

SDURLCache avec AFNetworking et mode hors ligne ne fonctionne pas

J'utilise AFNetworking y SDURLCache pour toutes mes opérations de réseau.

J'ai SDURLCache réglé comme ceci :

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];

Toutes mes demandes utilisent cachePolicy NSURLRequestUseProtocolCachePolicy qui, selon la documentation d'Apple, fonctionne comme suit :

Si une NSCachedURLResponse n'existe pas pour la demande, alors les données sont récupérées depuis la source d'origine. données sont récupérées à partir de la source d'origine. S'il existe une réponse en cache pour la demande, le système de chargement d'URL vérifie la réponse pour déterminer si elle spécifie que le contenu doit être revalidé. Si le contenu doit être revalidé, une connexion est établie avec la source d'origine pour voir si elle a changé. S'il n'a pas changé, alors la réponse est renvoyée depuis le cache local. S'il a changé, les données sont extraites de la source d'origine.

Si la réponse à la mise en cache ne précise pas que le contenu doit être revalidé, l'âge ou l'expiration maximum spécifié dans la réponse est examinée. Si la réponse de l'antémémoire est suffisamment récente, la réponse est renvoyée depuis le cache local. est renvoyée depuis le cache local. Si la réponse est Si la réponse est périmée, la source d'origine est vérifiée pour voir si elle est plus récente. données plus récentes. Si des données plus récentes sont disponibles, elles sont extraites de la source d'origine. source d'origine, sinon elles sont renvoyées depuis le cache.

Tout fonctionne donc parfaitement, même en mode avion, tant que le cache n'est pas périmé. Lorsque le cache expire (max-age et autres), le bloc d'échec est appelé.

J'ai creusé un peu à l'intérieur de la SDURLCache et cette méthode renvoie une réponse avec des données valides (j'ai transformé les données en une chaîne de caractères qui contient les informations mises en 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;
}

Donc, à ce stade, je n'ai aucune idée de ce qu'il faut faire, car je pense que la réponse est gérée par le système d'exploitation et ensuite AFNetworking reçoit un

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

à l'intérieur de AFURLConnectionOperation .

12voto

Ecarrion Points 2726

J'ai finalement trouvé une solution pas si moche :

Premier

Si vous utilisez IOS5/IOS6, vous pouvez abandonner SDURLCache et utiliser la version native :

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

Mais rappelez-vous que dans IOS5 les requêtes https ne seront pas mises en cache dans IOS6 elles le seront.

Deuxièmement

Nous avons besoin d'ajouter les cadres suivants à notre Prefix.pch pour que AFNetworking puisse commencer à surveiller notre connexion internet.

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

Troisièmement

Nous avons besoin d'une instance d'AFHTTPClient pour pouvoir intercepter chaque requête sortante et modifier son comportement. 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;
}

Avec ces bouts de code, nous pouvons maintenant détecter quand le wifi/3g est indisponible et spécifier la requête pour utiliser toujours le cache quoi qu'il arrive. (Mode hors ligne)

Notes

  • Je ne sais toujours pas quoi faire quand la networkReachabilityStatus es AFNetworkReachabilityStatusUnknown Cela peut se produire si une demande est faite dès le début de l'application et que AF n'a pas encore obtenu le statut internet.

  • N'oubliez pas que pour que cela fonctionne, le serveur doit définir les bons en-têtes de cache dans la réponse http.

UPDATE

Il semble que l'IOS6 ait des problèmes pour charger les réponses mises en cache dans les situations sans Internet, donc même si la demande est mise en cache et que la politique de cache de la demande est réglée sur NSURLRequestReturnCacheDataDontLoad la demande échouera.

Donc une solution de contournement peu élégante consiste à modifier (void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)error en AFURLConnectionOperation.m pour récupérer la réponse en cache si la demande échoue, mais uniquement pour des politiques de cache spécifiques.

- (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;
        }
    }
}

0voto

epolyakov Points 65

On ne peut pas dire grand-chose sans vos en-têtes HTTP, mais la raison la plus courante est NSURLProtocol forcer la revalidation avant de livrer la réponse en cache au WebView.

Veuillez jeter un coup d'œil ici : http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588

0voto

Dave Points 3228

On dirait que vous voulez que la requête aboutisse, même si le cache indique que les données ont expiré et doivent être récupérées sur le serveur. Vous aurez peut-être la possibilité de définir la politique de mise en cache (politique différente selon qu'il s'agit de données en ligne ou hors ligne) de certaines pages Web de l'entreprise. demande où vous préférez utiliser des données périmées plutôt que d'échouer.

NSMutableURLRequest -> setCachePolicy

On dirait que NSURLRequestReturnCacheDataDontLoad est la politique que vous voulez pour le mode hors ligne.

J'espère que cela vous aidera !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X