i'm trying to implement a NSURLCache with AFHTTPRequestOperation, this is what i have done:
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:50 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
....
}
Then to use it i do this:
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30.0f];
UIImage *image = [[UIImageView sharedImageCache] cachedImageForRequest:urlRequest];
if (image != nil) {
//Cached image
} else {
AFHTTPRequestOperation *postOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
postOperation.responseSerializer = [AFImageResponseSerializer serializer];
[postOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
UIImage *image = responseObject;
[[UIImageView sharedImageCache] cacheImage:image forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
}];
[postOperation start];
}
the first time i open the view when there is the code above download the image and store it in the cache, then i close the view and i open it again and read it from the cache, instead if i close the app, and i try again the image is downloaded again, so how i can create a NSURLCache persistent?
EDIT:
I have tried what Duncan Bubbage suggest, and i have add a cache path instead of the nil, and i have do it in this way:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = [paths objectAtIndex:0];
cachePath = [cachePath stringByAppendingPathComponent:#"temp_img"];
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:50 * 1024 * 1024 diskPath:cachePath];
[NSURLCache setSharedURLCache:URLCache];
but i still cannot cache perstitently across session, what i have wrong? i have also check in the Finder the Cache Folder, and there is the temp_img folder, i have tried to create the folder manually with NSFileManager and then pass the path, but the folder temp_img remain empty...
Are you sure it is valid to set diskPath:nil when you are wanting to cache to disk persistently across sessions? The parameter is optional but that may only be for the use case of caching in memory only, in which case you'd expect to see what you're seeing here?
Alternatively, the Apple documentation for this method states:
NOTE
In iOS, the on-disk cache may be purged when the system runs low on disk space, but only when your app is not running.
This could just be expected behaviour?
Related
I am using AFNetworking Library,
How to download the large amount of data using AFNetworking Library to show the better performance?
I have followed the following steps:
".zip" files downloading the content from server.
I have added one url , like this i have so many url's to download the content from the server. It takes too much time. How to perform the fast downloading from server.
My Query is: If the url was not found on server, then it executes failure state.
but the ".zip" is storing in documents folder with empty content like zerobytes.
while am trying to extract the .zip it shows .cpgz format.
Let us know how to resolve this?
-(void)downloadSingFile:(NSUInteger )activityAtIndex totalCount:(NSUInteger )lessonTotalCount{
// http://118.102.131.158/IYG_wrapper/IYG_updated/IYG_G7_L03/IYG_UN_Mall.zip
NSString *activityUrl = [filterObjects objectAtIndex:activityAtIndex];
NSURL *zipFileAtUrl = [NSURL URLWithString:[activityUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
BOOL success = [libraryObj createFolderWithName:rootName];
if (success) {
activityPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:rootName] stringByAppendingPathComponent:[zipFileAtUrl lastPathComponent]];
}
NSURLRequest *request = [NSURLRequest requestWithURL:zipFileAtUrl];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:activityPath append:NO];
[operation setCompletionBlock:^{
sleep(1);
NSLog(#"sucessfully downloaded file %d",activityIndx);
[self extractSingleActivityAtindex:activityIndx];
}];
[operation start];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float percentDone = ((float)(totalBytesRead) / (float)(totalBytesExpectedToRead));
self.tableView.alpha = 0.7;
progressView.hidden = NO;
progressAlert.hidden = NO;
progressAlert.labelText = #"Downloading..";
[self updateProgress:percentDone];
NSLog(#"progress float value is: %.2f",percentDone);
}];
}
1.Please let us know, now i dont want to download the which is exist in documents folder path and also let us know whether the the file has been downloaded sucessfully or not.
A couple of thoughts:
If you want to know if it succeeded or not, rather than using setCompletionBlock, you should use setCompletionBlockWithSuccess:failure:.
In a related topic, I might be inclined to download the file to some temporary file (in the NSTemporaryDirectory() folder) and only once it was successfully downloaded, move it to the Documents folder. You want to completely eliminate the possibility of any in-progress download resulting in a file in Documents.
If you don't want to download a file that already exists in your Documents folder, then you have to program that logic yourself (i.e. use NSFileManager to see if the file exists, and if not, only then initiate the download).
Eliminate the sleep call. You never want to sleep (esp on the main thread). If you really want to trigger something to happen one second later, use dispatch_after:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"sucessfully downloaded file %d",activityIndx);
[self extractSingleActivityAtindex:activityIndx];
});
You need to take care of below points to achieve your functionality
Check file is already exist or not before download
Implement failure block and remove file at your activityPath
For reference, please check below code snippet.
-(void)downloadSingFile:(NSUInteger )activityAtIndex totalCount:(NSUInteger )lessonTotalCount{
// http://118.102.131.158/IYG_wrapper/IYG_updated/IYG_G7_L03/IYG_UN_Mall.zip
NSString *activityUrl = [filterObjects objectAtIndex:activityAtIndex];
NSURL *zipFileAtUrl = [NSURL URLWithString:[activityUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
BOOL success = [libraryObj createFolderWithName:rootName];
if (success) {
activityPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:rootName] stringByAppendingPathComponent:[zipFileAtUrl lastPathComponent]];
}
if ([[NSFileManager defaultManager]fileExistsAtPath:activityPath]) {
NSLog(#"File Already Exist");
[self extractSingleActivityAtindex:activityIndx];
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:zipFileAtUrl];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:activityPath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
sleep(1);
NSLog(#"sucessfully downloaded file %d",activityIndx);
dispatch_async(dispatch_get_main_queue(), ^() {
[self extractSingleActivityAtindex:activityIndx];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// NSLog(#"ERR: %#", [error description]);
[[NSFileManager defaultManager]removeItemAtPath:activityPath error:nil];
}];
[operation start];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float percentDone = ((float)(totalBytesRead) / (float)(totalBytesExpectedToRead));
self.tableView.alpha = 0.7;
progressView.hidden = NO;
progressAlert.hidden = NO;
progressAlert.labelText = #"Downloading..";
[self updateProgress:percentDone];
NSLog(#"progress float value is: %.2f",percentDone);
}];
}
I have an app where I'm downloading quite a few images for display later on.
The app needs to function even when there's no internet connection, so the images are loaded at one point and persisted using NSURLCache.
This is a neat solution since I can use normal networking libraries and easily take advantage of custom cache settings.
I realize that this type of caching doesn't guarantee that the files are persisted until they expire since it's up to the system to release cache whenever it deems necessary. That's not a huge issue since the images should be able to be re-downloaded and hence re-persisted.
However, I've noticed that it decides to randomly release images from cache, and it doesn't seem to persist them when I download them again. I'm not sure where I'm going wrong.
First of all, I define the cache capacity like so:
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:50 * 1024 * 1024
diskCapacity:200 * 1024 * 1024
diskPath:#"netcache"];
[NSURLCache setSharedURLCache:URLCache];
(50MB in memory and 200MB on disk)
When downloading the images (using AFNetworking) I modify the response headers to set Cache-Control to max-age=31536000 which means it should cache the response for one year.
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Download completed
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Download failed
}];
[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
NSURLResponse *response = cachedResponse.response;
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse*)response;
NSDictionary *headers = HTTPResponse.allHeaderFields;
NSMutableDictionary *modifiedHeaders = headers.mutableCopy;
modifiedHeaders[#"Cache-Control"] = #"max-age=31536000"; // 1 year in seconds
NSHTTPURLResponse *modifiedHTTPResponse = [[NSHTTPURLResponse alloc]
initWithURL:HTTPResponse.URL
statusCode:HTTPResponse.statusCode
HTTPVersion:#"HTTP/1.1"
headerFields:modifiedHeaders];
return [[NSCachedURLResponse alloc] initWithResponse:modifiedHTTPResponse data:cachedResponse.data userInfo:cachedResponse.userInfo storagePolicy:NSURLCacheStorageAllowed];
}];
[self.operationQueue addOperation:operation];
...and yet it seems like images are released and won't even get "re-cached" when downloaded again. (I believe they are reported as cached, even though they won't load when trying to display them when the device doesn't have any connection.)
The images are later displayed using AFNetworking's UIImageView+AFNetworking.h category, like so:
[self.imageView setImageWithURL:url];
Any ideas?
I have faced the same problem. First of all you can use AFURLSessionManager's method
- (void)setDataTaskWillCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
to set cache block for all requests at the same time.
Second - check that 'Cache-Control' field in response headers is 'public'. For example:
[someAFHTTPSessionManager setDataTaskWillCacheResponseBlock:^NSCachedURLResponse *(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse)
{
NSHTTPURLResponse *resp = (NSHTTPURLResponse*)proposedResponse.response;
NSMutableDictionary *newHeaders = [[resp allHeaderFields] mutableCopy];
if (newHeaders[#"Cache-Control"] == nil) {
newHeaders[#"Cache-Control"] = #"public";
}
NSHTTPURLResponse *response2 = [[NSHTTPURLResponse alloc] initWithURL:resp.URL statusCode:resp.statusCode HTTPVersion:#"1.1" headerFields:newHeaders];
NSCachedURLResponse *cachedResponse2 = [[NSCachedURLResponse alloc] initWithResponse:response2
data:[proposedResponse data]
userInfo:[proposedResponse userInfo]
storagePolicy:NSURLCacheStorageAllowed];
return cachedResponse2;
}];
Third: If image's size is bigger than 5% of total cache space it will not be cached. Also it seems that there are some additional hidden rules.
In my application i am trying to download thousands of images (each image size with a maximum of 3mb) and 10's of videos (each video size with a maximum of 100mb) and saving it in Documents Directory.
To achieve this i am using AFNetworking
Here my problem is i am getting all the data successfully when i am using a slow wifi (around 4mbps), but the same downloading if i am doing under a wifi with a speed of 100mbps the application is getting memory warning while downloading images and memory pressure issue while downloading videos and then application is crashing.
-(void) AddVideoIntoDocument :(NSString *)name :(NSString *)urlAddress{
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlAddress]];
[theRequest setTimeoutInterval:1000.0];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:theRequest];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
//NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead);
}];
[operation start];
}
-(void)downloadRequestedImage : (NSString *)imageURL :(NSInteger) type :(NSString *)imgName{
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
[theRequest setTimeoutInterval:10000.0];
AFHTTPRequestOperation *posterOperation = [[AFHTTPRequestOperation alloc] initWithRequest:theRequest];
posterOperation.responseSerializer = [AFImageResponseSerializer serializer];
[posterOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSLog(#"Response: %#", responseObject);
UIImage *secImg = responseObject;
if(type == 1) { // Delete the image from DB
[self removeImage:imgName];
}
[self AddImageIntoDocument:secImg :imgName];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image request failed with error: %#", error);
}];
[posterOperation start];
}
The above code i am looping according to the number of videos and images that i have to download
What is the reason behind that behaviour
I even have screen shots of memory allocation for both the scenarios
Please Help
Adding code for saving the downloaded images also
-(void)AddImageIntoDocument :(UIImage *)img :(NSString *)str{
if(img) {
NSData *pngData = UIImageJPEGRepresentation(img, 0.4);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePathName =[[paths objectAtIndex:0]stringByAppendingPathComponent:str];
[pngData writeToFile:filePathName atomically:YES];
}
else {
NSLog(#"Network Error while downloading the image!!! Please try again.");
}
}
The reason for this behavior is that you're loading your large files into memory (and presumably it's happening quickly enough that you app isn't having a chance to respond to memory pressure notifications).
You can mitigate this by controlling the peak memory usage by not loading these downloads into memory. When download large files, it's often better to stream them directly to persistent storage. To do this with AFNetworking, you can set the outputStream of the AFURLConnectionOperation, and it should stream the contents directly to that file, e.g.
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:[url lastPathComponent]]; // use whatever path is appropriate for your app
operation.outputStream = [[NSOutputStream alloc] initToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"successful");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
}];
[self.downloadQueue addOperation:operation];
BTW, you'll notice that I'm not just calling start on these requests. Personally, I always add them to a queue for which I've specified the maximum number of concurrent operations:
self.downloadQueue = [[NSOperationQueue alloc] init];
self.downloadQueue.maxConcurrentOperationCount = 4;
self.downloadQueue.name = #"com.domain.app.downloadQueue";
I think this is less critical regarding memory usage than the streaming of the results directly to a outputStream using persistent storage, but I find this is another mechanism for managing system resources when initiating many concurrent requests.
You can start using NSURLSession's downloadTask.
I think this will resolve your issue.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://someSite.com/somefile.zip"]];
[[NSURLSession sharedSession] downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
// Use location (it's file URL in your system)
}];
I am developing an app that download data from the web through json. I set the cache policy to use the cache as I have seen in tutorials and posts. Well, when the app starts, it downloads the data once and if I close the view and reopen it in flight mode, the data is loaded from cached. But if I close the app and then restart it, do not load from cache and request the server again (or show error if flight mode is on). I need caching data on disk for offline usage... help please
here is my code:
AppDelegate.m
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024
diskCapacity:100 * 1024 * 1024
diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
and the request:
NSString *string = #"http://********.es/api/get_posts/?post_type=listing";
NSURL *url = [NSURL URLWithString:string];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
NSCachedURLResponse * newCachedResponse = [[NSCachedURLResponse alloc]
initWithResponse:cachedResponse.response
data:cachedResponse.data
userInfo:cachedResponse.userInfo
storagePolicy:NSURLCacheStorageAllowed];
return newCachedResponse;
}];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
_lista = responseObject[#"posts"];
[self.tableView reloadData];
//NSLog(#"%#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Data"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
[operation start];
I have tried a lot of solutions on the web and no one works as I would like, so I think the problem is something wrong in my code...
I think the functionality you are wanting here is going to require using core data or writing your own caching using NSFileManager. Figure out the best approach for your app. There is a pretty cool article about NSFileManager by Mattt Thompson here
I have a test app setup and it successfully downloads content from the network even if the user switches apps while a download is in progress. Great, now I have background downloads in place. Now I want to add caching. There is no point to me downloading images more than once, b/c of system design, given an image URL I can tell you the content behind that URL will never change. So, now I want to cache the results of my download using apple's built in in-memory/on-disk cache that I've read so much about (as opposed to me saving the file manually in NSCachesDirectory and then checking there before making new request, ick). In an attempt to get caching working on top of this working code, I added the following code:
- (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;
}
When I create my session, I've added two NEW lines (URLCache and 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;
}
Then, just to be ultra redundant in an attempt to see caching success I switched my NSURLRequest line from
// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I've replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line
Now, when I go to download the item a 2nd time, the experience is exaclty like the first!! Takes a long time to download and progress bar is animated slow and steady like an original download. I want the data in the cache immediately!! What am I missing???
----------------------------UPDATE----------------------------
Okay, thanks to Thorsten's answer, I've added the following two lines of code to my didFinishDownloadingToURL delegate method:
- (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
*/
}
This is great. It confirms the cache is growing. I presume since I'm using a downloadTask (downloads to file as opposed to memory), that that's why DiskCache is growing and not memory cache first? I figured everything would go to memory cache until that overflowed and then disk cache would be used and that maybe memory cache was written to disk before the OS kills the app in the background to free up memory. Am I misunderstanding how Apple's cache works?
This is a step forward for sure, but the 2nd time I download the file it takes just as long as the first time (maybe 10 seconds or so) and the following method DOES get executed again:
- (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?
}
What do you make of my edits above? Why am I not seeing the file returned very quickly on subsequent requests?
How can I confirm if the file is being served from the cache on the 2nd request?
Note that the following SO post helped me solve my problem: Is NSURLCache persistent across launches?
- (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!
}
In addition to the sleep(1) line, also note the size of my cache; 500MB.
According to docs you need a cache size that is way bigger than what you're trying to cache.
The response size is small enough to reasonably fit within the cache.
(For example, if you provide a disk cache, the response must be no
larger than about 5% of the disk cache size.)
So for example if you want to be able to cache a 10MB image, then a cache size of 10MB or even 20MB will not be enough. You need 200MB.
Honey's comment below is evidence that Apple is following this 5% rule. For an 8Mb he had to set his cache size to minimum 154MB.
Solution - first get all info u need it something like this
- (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;
}
After you need to download each file - in my case - make parsing of response and download images. Also before making request you need to check if cache already have response for your request - something like this
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];
}
After that you can also check result with xCode Network Analyzer.
Also note as mentionted by #jcaron and documented by Apple
NSURLSession won't attempt to cache a file larger than 5% of the cache
size
Result something like
Once you set the cache and the session, you should use the session-methods to download your data:
- (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
}
After the first call, the image is cached.