web-dev-qa-db-fra.com

Comment mettre en cache à l'aide de NSURLSession et NSURLCache. Ca ne fonctionne pas

J'ai une configuration d'application de test et elle télécharge avec succès du contenu depuis le réseau même si l'utilisateur change d'appli pendant qu'un téléchargement est en cours. Super, maintenant j'ai des téléchargements en arrière-plan en place. Maintenant, je veux ajouter la mise en cache. Cela ne sert à rien de télécharger des images plus d'une fois, b/c de la conception du système, étant donné une URL d'image, je peux vous dire que le contenu derrière cette URL ne changera jamais. Donc, maintenant je veux mettre en cache les résultats de mon téléchargement en utilisant le cache intégré en mémoire/sur disque d'Apple sur lequel j'ai tant lu (par opposition à moi d'enregistrer le fichier manuellement dans NSCachesDirectory et de le vérifier avant de faire de nouveaux demande, ick). Dans un tentative pour faire fonctionner la mise en cache par-dessus ce code de travail, j'ai ajouté le code suivant:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    // Set app-wide shared cache (first number is megabyte value)
    [NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:60 * 1024 * 1024
                                                                diskCapacity:200 * 1024 * 1024
                                                                    diskPath:nil]];

    return YES;
}

Lorsque je crée ma session, j'ai ajouté deux NOUVELLES lignes (URLCache et requestCachePolicy).

// Helper method to get a single session object
- (NSURLSession *)backgroundSession
{
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.example.Apple-samplecode.SimpleBackgroundTransfer.BackgroundSession"];
        configuration.URLCache = [NSURLCache sharedURLCache]; // NEW LINE ON TOP OF OTHERWISE WORKING CODE
        configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;  // NEW LINE ON TOP OF OTHERWISE WORKING CODE
        session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    });
    return session;
}

Ensuite, juste pour être ultra redondant afin de voir le succès de la mise en cache, j'ai changé ma ligne NSURLRequest de

// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I've replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line

Maintenant, quand je vais télécharger l'article une deuxième fois, l'expérience est exactement comme la première !! Le téléchargement prend beaucoup de temps et la barre de progression est animée lentement et régulièrement comme un téléchargement original. Je veux les données dans le cache immédiatement !! Qu'est-ce que je rate???

----------------------------MISE À JOUR--------------------- -------

D'accord, grâce à la réponse de Thorsten, j'ai ajouté les deux lignes de code suivantes à ma méthode déléguée didFinishDownloadingToURL:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL {

    // Added these lines...
    NSLog(@"DiskCache: %@ of %@", @([[NSURLCache sharedURLCache] currentDiskUsage]), @([[NSURLCache sharedURLCache] diskCapacity]));
    NSLog(@"MemoryCache: %@ of %@", @([[NSURLCache sharedURLCache] currentMemoryUsage]), @([[NSURLCache sharedURLCache] memoryCapacity]));
    /*
    OUTPUTS:
    DiskCache: 4096 of 209715200
    MemoryCache: 0 of 62914560
    */
}

C'est bien. Cela confirme que le cache augmente. Je suppose que puisque j'utilise une tâche de téléchargement (téléchargements dans un fichier par opposition à la mémoire), c'est pourquoi DiskCache se développe et non le cache de mémoire en premier? J'ai pensé que tout irait dans le cache de la mémoire jusqu'à ce que le cache de débordement soit utilisé et que le cache de mémoire ait peut-être été écrit sur le disque avant que le système d'exploitation ne tue l'application en arrière-plan pour libérer de la mémoire. Suis-je en train de mal comprendre le fonctionnement du cache d'Apple?

C'est certainement un pas en avant, mais la 2ème fois que je télécharge le fichier cela prend juste autant de temps que la première fois (peut-être 10 secondes environ) et la méthode suivante est exécutée à nouveau:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // This shouldn't execute the second time around should it? Even if this is supposed to get executed a second time around then shouldn't it be lightning fast? It's not.
    // On all subsequent requests, it slowly iterates through the downloading of the content just as slow as the first time. No caching is apparent. What am I missing?
}

Que pensez-vous de mes modifications ci-dessus? Pourquoi ne vois-je pas le fichier renvoyé très rapidement lors de demandes ultérieures?

Comment puis-je confirmer si le fichier est servi à partir du cache à la 2e demande?

26
John Erck

Notez que le message suivant SO m'a aidé à résoudre mon problème: NSURLCache est-il persistant à travers les lancements?

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Set app-wide shared cache (first number is megabyte value)
    NSUInteger cacheSizeMemory = 500*1024*1024; // 500 MB
    NSUInteger cacheSizeDisk = 500*1024*1024; // 500 MB
    NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"];
    [NSURLCache setSharedURLCache:sharedCache];
    sleep(1); // Critically important line, sadly, but it's worth it!
}

En plus de la ligne sleep(1), notez également la taille de mon cache; 500 Mo.

Selon docs vous avez besoin d'une taille de cache bien supérieure à celle que vous essayez de mettre en cache.

La taille de la réponse est suffisamment petite pour tenir raisonnablement dans le cache. (Par exemple, si vous fournissez un cache disque, la réponse ne doit pas dépasser environ 5% de la taille du cache disque.)

Ainsi, par exemple, si vous souhaitez pouvoir mettre en cache une image de 10 Mo, une taille de cache de 10 Mo ou même de 20 Mo ne sera pas suffisante. Vous avez besoin de 200 Mo. Le commentaire de Honey ci-dessous est la preuve que Apple suit cette règle de 5%. Pour un 8 Mo, il devait régler sa taille de cache au minimum 154 Mo.

32
John Erck

Solution - obtenez d'abord toutes les informations dont vous avez besoin quelque chose comme ça

- (void)loadData
{
    if (!self.commonDataSource) {
        self.commonDataSource = [[NSArray alloc] init];
    }

    [self setSharedCacheForImages];

    NSURLSession *session = [self prepareSessionForRequest];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[BaseURLString stringByAppendingPathComponent:@"app.json"]]];
    [request setHTTPMethod:@"GET"];
    __weak typeof(self) weakSelf = self;
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (!error) {
            NSArray *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
            weakSelf.commonDataSource = jsonResponse;
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf updateDataSource];
            });
        }
    }];
    [dataTask resume];
}

- (void)setSharedCacheForImages
{
    NSUInteger cashSize = 250 * 1024 * 1024;
    NSUInteger cashDiskSize = 250 * 1024 * 1024;
    NSURLCache *imageCache = [[NSURLCache alloc] initWithMemoryCapacity:cashSize diskCapacity:cashDiskSize diskPath:@"someCachePath"];
    [NSURLCache setSharedURLCache:imageCache];
}

- (NSURLSession *)prepareSessionForRequest
{
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    [sessionConfiguration setHTTPAdditionalHeaders:@{@"Content-Type": @"application/json", @"Accept": @"application/json"}];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
    return session;
}

Après avoir besoin de télécharger chaque fichier - dans mon cas - faites une analyse de la réponse et téléchargez des images. Avant de faire une demande, vous devez également vérifier si le cache a déjà une réponse à votre demande - quelque chose comme ça

NSString *imageURL = [NSString stringWithFormat:@"%@%@", BaseURLString ,sourceDictionary[@"thumb_path"]];
NSURLSession *session = [self prepareSessionForRequest];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
[request setHTTPMethod:@"GET"];

NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse.data) {
    UIImage *downloadedImage = [UIImage imageWithData:cachedResponse.data];
    dispatch_async(dispatch_get_main_queue(), ^{
        cell.thumbnailImageView.image = downloadedImage;
    });
} else {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (!error) {
        UIImage *downloadedImage = [UIImage imageWithData:data];
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.thumbnailImageView.image = downloadedImage;
        });
    }
}];
    [dataTask resume];
}

Après cela, vous pouvez également vérifier le résultat avec xCode Network Analyzer.

Notez également comme mentionné par @jcaron et documenté par Apple

NSURLSession ne tentera pas de mettre en cache un fichier supérieur à 5% de la taille du cache

Résultat quelque chose comme

enter image description here

10
gbk

Une fois que vous avez défini le cache et la session, vous devez utiliser les méthodes de session pour télécharger vos données:

- (IBAction)btnClicked:(id)sender {
    NSString *imageUrl = @"http://placekitten.com/1000/1000";
    NSURLSessionDataTask* loadDataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        UIImage *downloadedImage = [UIImage imageWithData:data];
        NSLog(@"ImageSize: %f, %f", downloadedImage.size.width, downloadedImage.size.height);
        NSLog(@"DiskCache: %i of %i", [[NSURLCache sharedURLCache] currentDiskUsage], [[NSURLCache sharedURLCache] diskCapacity]);
        NSLog(@"MemoryCache: %i of %i", [[NSURLCache sharedURLCache] currentMemoryUsage], [[NSURLCache sharedURLCache] memoryCapacity]);
    }];
    [loadDataTask resume]; //start request
}

Après le premier appel, l'image est mise en cache.

2
Thorsten