ios - Cómo almacenar en caché utilizando NSURLSession y NSURLCache. No funciona
iphone nsurlsessionconfiguration (3)
Solución: primero obtenga toda la información que necesita algo como esto
- (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;
}
Después de que necesite descargar cada archivo, en mi caso, haga un análisis de la respuesta y descargue las imágenes. Además, antes de realizar la solicitud, debe comprobar si la memoria caché ya tiene respuesta para su solicitud, algo como esto
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];
}
Después de eso, también puede comprobar el resultado con xCode Network Analyzer.
También tenga en cuenta como lo mencionó @jcaron y developer.apple.com/library/prerelease/ios/documentation/…
NSURLSession no intentará almacenar en caché un archivo de más del 5% del tamaño del caché
Resultado algo como
Tengo una configuración de la aplicación de prueba y descarga contenido de la red con éxito, incluso si el usuario cambia de aplicación mientras se realiza una descarga. Genial, ahora tengo descargas de fondo en su lugar. Ahora quiero añadir caché. No tiene sentido que descargue imágenes más de una vez, b / c del diseño del sistema, dada una URL de imagen, puedo decirle que el contenido detrás de esa URL nunca cambiará. Por lo tanto, ahora quiero guardar en caché los resultados de mi descarga utilizando la memoria caché integrada en la memoria / en disco de Apple, sobre la que he leído mucho (a diferencia de mí, guardar el archivo manualmente en NSCachesDirectory y luego consultar allí antes de hacer nuevos solicitud, ick). En un intento de hacer que el almacenamiento en caché funcione sobre este código de trabajo, agregué el siguiente código:
- (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;
}
Cuando creo mi sesión, agregué dos NUEVAS líneas (URLCache y 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;
}
Luego, solo para ser ultra redundante en un intento de ver el éxito del almacenamiento en caché, cambié mi línea NSURLRequest desde
// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I''ve replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line
Ahora, cuando voy a descargar el artículo por segunda vez, ¡la experiencia es como la primera! La barra de progreso tarda mucho tiempo en descargarse y la animación es lenta y constante, como una descarga original. Quiero que los datos en la caché de inmediato !! ¿¿¿Qué me estoy perdiendo???
----------------------------ACTUALIZAR--------------------- -------
Bien, gracias a la respuesta de Thorsten, he agregado las siguientes dos líneas de código a mi método de delegado 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
*/
}
Esto es genial. Confirma que el caché está creciendo. Supongo que ya que estoy usando una función de descarga (descargas en un archivo en lugar de en la memoria), ¿es por eso que DiskCache está creciendo y no la memoria caché primero? Pensé que todo iría al caché de memoria hasta que se desbordara y luego se usaría el caché de disco y que tal vez el caché de memoria se escribiera en el disco antes de que el sistema operativo elimine la aplicación en segundo plano para liberar memoria. ¿Estoy malentendido cómo funciona el caché de Apple?
Este es un paso adelante con seguridad, pero la segunda vez que descargo el archivo lleva la primera vez (aproximadamente 10 segundos) y el siguiente método se ejecuta de nuevo:
- (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?
}
¿Qué piensas de mis ediciones anteriores? ¿Por qué no veo el archivo devuelto muy rápidamente en las solicitudes posteriores?
¿Cómo puedo confirmar si el archivo se entrega desde la memoria caché en la segunda solicitud?
Tenga en cuenta que la siguiente publicación SO me ayudó a resolver mi problema: ¿NSURLCache es persistente en todos los lanzamientos?
- (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!
}
Además de la línea de sleep(1)
, también tenga en cuenta el tamaño de mi caché; 500MB.
De acuerdo con los docs , necesitas un tamaño de caché que sea mucho más grande de lo que estás intentando almacenar en caché.
El tamaño de la respuesta es lo suficientemente pequeño como para caber razonablemente dentro de la caché. (Por ejemplo, si proporciona un caché de disco, la respuesta no debe ser mayor que aproximadamente el 5% del tamaño del caché de disco).
Así, por ejemplo, si desea poder almacenar en caché una imagen de 10 MB, entonces un tamaño de caché de 10 MB o incluso de 20 MB no será suficiente. Necesitas 200MB. El comentario de Honey a continuación es una evidencia de que Apple está siguiendo esta regla del 5%. Para un 8Mb tuvo que establecer el tamaño de su caché a un mínimo de 154MB.
Una vez que configure el caché y la sesión, debe usar los métodos de sesión para descargar sus datos:
- (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
}
Después de la primera llamada, la imagen se almacena en caché.