I am using NSURLRequest with CachePolicy to download a plist in NSData. When I change the content of my plist my app is ignoring this and still presents the content which is cached. How long does the cache persist? If so is there an option to say how long the cache data persists?
Is there a way to check in NSURLRequest if the data on the server is newer than the cache load the data from the server or if it is equal to cache use the cache?
Have a look at Controlling Response Caching in the URLLoadingSystem docs.
You can add your own date in the delegate methods
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
Much more easy with the caching system is ASIHTTPRequest. I recommend to use this URL Loading System.
From the apple docs:
The example in Listing 6 prevents the caching of https responses. It
also adds the current date to the user info dictionary for responses
that are cached.
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
NSCachedURLResponse *newCachedResponse = cachedResponse;
if ([[[[cachedResponse response] URL] scheme] isEqual:#"https"]) {
newCachedResponse = nil;
} else {
NSDictionary *newUserInfo;
newUserInfo = [NSDictionary dictionaryWithObject:[NSCalendarDate date]
forKey:#"Cached Date"];
newCachedResponse = [[[NSCachedURLResponse alloc]
initWithResponse:[cachedResponse response]
data:[cachedResponse data]
userInfo:newUserInfo
storagePolicy:[cachedResponse storagePolicy]]
autorelease];
}
return newCachedResponse;
}
Related
I have seen several posts for this question and I cant figure out how to get this working. For example, this post NSCachedURLResponse returns object, but UIWebView does not interprets content
suggests that we subclass and override cachedResponseForRequest and modify the NSURLRequest before returning a cached response (not sure why this needs to be done).
This is the code i have in my NSURLCache subclass:
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
NSCachedURLResponse *resp = [super cachedResponseForRequest:request];
if (resp != nil && resp.data.length > 0) {
NSDictionary *headers = #{#"Access-Control-Allow-Origin" : #"*", #"Access-Control-Allow-Headers" : #"Content-Type"};
NSHTTPURLResponse *urlresponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:#"1.1" headerFields:headers];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:urlresponse data:resp.data];
return cachedResponse;
} else {
return nil;
}
}
Now when i load the request via the webview and I'm offline, the delegate didFailLoadWithError gets called (it was not able to load), but before that delegate method is called, my cachedResponseForRequest method gets called and the cached response has some html however my didFailLoadWithError method still gets called. So my question is, should the webViewDidFinishLoad should get called if we return the cached response from the NSURLCache, if not how do i make this work?
I have this in my didFinishLaunchingWithOptions:
NSUInteger cacheSizeMemory = 500*1024*1024; // 500 MB
NSUInteger cacheSizeDisk = 500*1024*1024; // 500 MB
NSURLCache *sharedCache = [[CustomCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
This is the flow i want working:
Launch the app.
Hit some url (lets say slickdeals.net)
Go offline kill the application
Launch the application
UIWebView attempts to hit url but device is offline and a returned cached response is returned.
We display cached response.
Any thoughts on what needs to change in my code is appreciated!
NSURLCache is designed for HTTP conform caching, and often behaves unexpectly. You must wrote your own cache for your use case, and implement a custom NSURLProtocol to use it. The protocol put the responses into your cache, and provides them from cache when offline. Rob Napier wrotes an excellent article about this.
I'm using AVFoundation framework for live streaming playback. Now I have a playlist like below
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:NO
#EXT-X-MEDIA-SEQUENCE:8148007
#EXT-X-TARGETDURATION:6
#EXT-X-PROGRAM-DATE-TIME:1972-04-14T08:51:01.497Z
I think AVPlayer make a request to get this playlist. Can I use classes in AVFoundation to extract EXT-X-TARGETDURATION and EXT-X-PROGRAM-DATE-TIME. If not, any other ways? Thanks
#EXT-X-PROGRAM-DATE-TIME is available via AVPlayerItem's currentDate property. It doesn't report the date of the tag directly, but rather the date of the last tag before the current playback position, plus any played interval since then.
#EXT-X-TARGETDURATION is not available directly. You will have to either load the playlist yourself, or insert yourself in the loading process. I've had success with AVAssetResourceLoader and AVAssetResourceLoaderDelegate. You can rewrite URL schemes to something the loader doesn't recognize (anything other than http or https), and performing the loading on the player's behalf.
I don't think AVFoundation provides access to all of those tags. However when I need to debug the streams, I use a custom NSURLProtocol to intercept all traffic. I think sometimes it's better to see the raw playlist and the HTTP response because AVPlayer does not give good error message(e.g. "The operation cannot be completed")
A good tutorial on NSURLProtocol can be found here: https://www.raywenderlich.com/59982/nsurlprotocol-tutorial
First let the url loading system know you can handle HLS request by calling [NSURLProtocol registerClass:/* your class */] and override +(BOOL)canInitWithRequest:
+(BOOL)canInitWithRequest:(NSURLRequest *)request {
BOOL handled = [[NSURLProtocol propertyForKey:#"handled" inRequest:request] boolValue];
return [request.URL.pathExtension isEqualToString:#"m3u8"] && !handled;
}
Then override -(void)startLoading in your custom URLProtocol
-(void)startLoading {
newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:#"handled" inRequest:newRequest];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:newRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
/* Inspect response body(playlist) and error */
NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
/* Return data and control to AVPlayer*/
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
}];
[task resume];
}
I download images and use it in my app. I have more then 100 images and I use it not all at a time. I will use cache. Then I load all images I save it to cache. Then I will go to the other parts of the app and I will use some that images.
I not really understand how the ios cache works and I have a question: after load all images and saving it to cache, how should I use this images, I mean, I need load them from cache or use instances of images that I have before saving it ti cache ?
And what caching strategy is more useful with AFNetworking 2.0?
AFNetworking has in-memory caching for images. If you have 100 images it could appropriate for you, could be not.
You should also read about NSURLCache here - http://nshipster.com/nsurlcache/.
If you use NSURLCache you do not have to think about caching - when you start downloading something that is already cached, system will just give you downloaded file.
UPDATE:
Time expiration for downloaded data is set by server in response. But you can edit or ignore it by NSURLConnectionDelegate method. Example:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)[cachedResponse response];
// Look up the cache policy used in our request
if([connection currentRequest].cachePolicy == NSURLRequestUseProtocolCachePolicy) {
NSDictionary *headers = [httpResponse allHeaderFields];
NSString *cacheControl = [headers valueForKey:#"Cache-Control"];
NSString *expires = [headers valueForKey:#"Expires"];
if((cacheControl == nil) && (expires == nil)) {
NSLog(#"server does not provide expiration information and we are using NSURLRequestUseProtocolCachePolicy");
return nil; // don't cache this
}
}
return cachedResponse;
}
I initialized NSURLCache in my app delegate like so:
int cacheSizeMemory = 4*1024*1024; // 4MB
int cacheSizeDisk = 32*1024*1024; // 32MB
MyServerCache *sharedCache = [[MyServerCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
[MyServerCache setSharedURLCache:sharedCache];
Then I create a request with default cache policy and call:
[NSURLConnection sendSynchronousRequest: urlRequest
returningResponse: &response
error: pError ];
The response is of type "application/json" and has "Cache-Control" set to "private, max-age=600". The response is only 874 bytes (shouldn't be too large to cache), however NSURLCache does not seem to work.
I have run this on a simulator and looked for the on-disk cache which doesn't seem to exist, so I subclassed NSURLCache (MyServerCache) and overrode the following methods to see if they were being called:
-(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
NSLog(#"Returning cached response for request: %#", request);
return [super cachedResponseForRequest:request];
}
-(void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
NSLog(#"Caching response for request: %#", request);
[super storeCachedResponse:cachedResponse forRequest:request];
}
No matter what I do (and I've tried several different API's and responses), NSURLCache never seems to cache anything. I can never find the on-disk cache in the file system, and the methods above are never called on my subclass of NSURLCache (MyServerCache).
I'd really like to use this rather than rolling my own cache, but I have no idea why it's not working or how to get it to work. Please help.
You have to create your request with the NSURLRequestUseProtocolCachePolicy and it works perfectly out-of-the box with NSURLCache.
NSURLRequestCachePolicy cachePolicy = NSURLRequestUseProtocolCachePolicy;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:cachePolicy
You can read about the NSURLRequestCachePolicy here: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLRequest_Class/#//apple_ref/c/tdef/NSURLRequestCachePolicy
A little bit confusing ist that iOS always returns a 200 - Success so you will never get a 304 - Resource not Modified when a response is coming from cache.
So it´s more easy to see the cache policy working when monitoring the server side.
You can use https://github.com/PaulEhrhardt/Simple-Cache-Test to validate cache-control, expires and ETag with a little local ruby server. It´s just a few lines to set-up. I tested both successfully with 7.1 and 8.4 iOS SDK.
I have an issue on how to refresh the UI for iOS apps. What I wanted to achieve is this:
Show data in UITableView based on data retrieved from web service
The web service should be called from a separate thread (not main thread)
After the data is retrieved, it will refresh the contents of UITableView with the retrieved data
It is due so that the UI will not hang or the app will not block user input while in the process of receiving data from the web service in bad network connection
To do that, I create the following source code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *myURL = [[NSURL alloc] initWithString:[Constant webserviceURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:60];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[self myparser] = [[MyXMLParser alloc] initXMLParser];
[parser setDelegate:myparser];
BOOL success = [parser parse];
if (success) {
// show XML data to UITableView
[_tableView performSelectorOnMainThread:#selector(reloadData) withObject:[myparser xmldata] waitUntilDone:NO];
}
else {
NSLog(#"Error parsing XML from web service");
}
}
==================
Is my implementation correct? Anybody know how to resolve it?
You would want to call
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
It will make the call to get the Data on a different thread then when the data pulled down or it had problems download data from the url it will call your handler block on the same thread as the original call was made.
Here is one way to use it: https://stackoverflow.com/a/9409737/1540822
You can also use
- (id)initWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate
And this will call one of your NSURLConnectionDelegate methods when data is downloaded in chucks. If you going to have large data then you may want to use this so that you don't spend too much time in the response.