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
Related
I am creating an iOS Application working in offline mode by using a AFNetworking library.
To manage a HTTP request I have defined a AFHTTPRequestOperationManager
- (AFHTTPRequestOperationManager *)requestOperationManager
{
if (!_requestOperationManager)
{
_requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:SERVICE_BASE_URL]];
}
return _requestOperationManager;
}
To load a HTTP request I have defined a method:
- (void)loadRequest
{
if (!self.internetConnectionAvaliable)
{
self.requestOperationManager.requestSerializer.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
}
NSString *url = SERVICE_METHOD_URL;
NSDictionary *parameters = #{#"password" : USER_PASSWORD, #"username" : USER_NAME};
[self.requestOperationManager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *htmlString = [NSString stringWithFormat:#"%#", [responseObject objectForKey:#"view"]];
[self.webView loadHTMLString:htmlString baseURL:nil];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"ERROR!!!" message:#"ERROR!!!" delegate:nil cancelButtonTitle:#"CANCEL" otherButtonTitles:nil];
[alertView show];
}];
}
When internet connection is enabled, AFNetworking loads desired data perfectly.
When internet connection is disabled, AFNetworking doesn't load any data. This makes me think, that data caching doesn't work as it should!
Does anyone knows why?
How to fix it?
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?
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 have a problem when users use my app and they lost connection or use airplane mode.
My app server side doesn't set any cache policy and for the time being I can't change it. I migrated from AFNetworking 1.x to 2.0 and now I'm using AFHTTPRequestOperationManager when making requests. The problem is that because I have no cache policy on the server side, every request is made to the server (which for now is fine) but if the user is not able to connect to my server, it doesn't load the cached request.
So, I'm trying the following, using AFHTTPRequestOperation directly like this:
NSURL *URL = [NSURL URLWithString:filePath];
NSMutableURLRequest *request = [[NSURLRequest requestWithURL:URL] mutableCopy];
if (![[AFNetworkReachabilityManager sharedManager] isReachable]) {
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[[NSOperationQueue mainQueue] addOperation:op];
This way, if AFNetworkReachabilityManager tells me there's no connectivity, I configured the cache policy for the request and it is loaded from the cache correctly.
The question is, is this the right approach to this situation?
You just need to subclass the AFHTTPSessionManager and check if the client is offline. Then you can change the cache policy or force the app to use cached data.