I'm using this line to get the contents of a URL:
NSString *result=[[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:URL]
encoding:NSUTF8StringEncoding
error:nil];
The problem is when there's a bad connection, it loads the contents from cache. Is there a way to avoid this behaviour? For example, clearing the cache or something.
First, it's not recommended to use initWithContentsOfURL:encoding:error to load data from a network resource.
Second, if you want to control caching behavior, you should be using an NSURLRequest. NSURLRequest allows you to customize the caching behavior of the request by setting the cachePolicy of the request. In your case, you want to use NSURLRequestReloadIgnoringLocalCacheData. An example of doing this synchronously using NSURLConnection would be:
NSString *result = nil;
NSData *data = nil;
NSURLResponse *response = nil;
NSURLError *error = nil;
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20L];
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (response != nil && [data length] > 0){
result = [NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
Note that this is a very naive implementation that does not check the HTTP status code returned, the mime-type of the response, or perform any error handling. It is also not a recommended practice to load network resources synchronously or to do so from the main thread. A better implementation would use sendAsynchronousRequest:completion: or NSURLSession.
However, it does demonstrate at a high level what you would need to do to answer your question: The NSURLRequest specifies that this request should never use the local cache, and the returned data is used to create an instance of NSString.
Simple cache-buster dummy parameter with random value added to URL should work.
And as #Josh-Caswell said, use NSURLRequest. Although in case of proxy servers, just using NSURLRequest may not help and you will still need cache-buster.
Related
I'm trying to persist data from a microsoft web service call to a file when my app starts up. If the app is able to complete the webservice request, it's storing the data in an NSData object. Assuming the data has been successfully requested and stored, I want to execute certain code that I would NOT want to if the webservice is unsuccessful.
My webservice request is as follows:
NSMutableURLRequest *request1 = [[NSMutableURLRequest alloc]init];
[request1 setURL:[NSURL URLWithString:#"---URL---"]];
[request1 setHTTPMethod:#"GET"];
NSURLResponse *request1Response;
NSData *request1Handler = [NSURLConnection sendSynchronousRequest:request1 returningResponse:&request1Response error:nil];
NSString *request1Reply = [[NSString alloc]initWithBytes:[request1Handler bytes] length:[request1Handler length] encoding:NSASCIIStringEncoding];
NSData *data1 = [request1Reply dataUsingEncoding:NSUTF16StringEncoding];
So basically, the response is dropped into that data1 object.
When connected to the internet, the request executes fine. The code that follows is wrapped in a if(data1){ conditional to make sure that the webservice request is successful before executing it. The problem is that when I disconnect from the internet(and cut off access to that webservice), the code inside the conditional is still being executed.
I tried comparing data1 to nil, logging data1 to do a direct comparison of the contents, etc, but I gather that that data1 object isn't nil; it probably contains some sort of failure message that I have thus far been unable to access. What can I do in the conditional or in the webservice request itself to figure out when the request fails?
Create an NSError object and pass it as argument to the sendSynchronousRequest: method, then if there is network or another error, the err object will populated with error information hence it will not be nil. That means you can check if(!err)contiune else there is an error
check the code:
NSMutableURLRequest *request1 = [[NSMutableURLRequest alloc]init];
[request1 setURL:[NSURL URLWithString:#"http://www.google.com"]];
[request1 setHTTPMethod:#"GET"];
//object where error will be saved
NSError *err;
NSURLResponse *request1Response;
NSData *request1Handler = [NSURLConnection sendSynchronousRequest:request1 returningResponse:&request1Response error:&err];//pass the err here by reference
NSString *request1Reply = [[NSString alloc]initWithBytes:[request1Handler bytes] length:[request1Handler length] encoding:NSASCIIStringEncoding];
NSData *data1 = [request1Reply dataUsingEncoding:NSUTF16StringEncoding];
and then you can check here:
if (err){
NSLog(#"error: %#", err);
}
You should at least be checking for an error response by populating the error pointer parameter.
Ideally you should be using a different API which gives you access to more details such as the response HTTP status code which you should be using to determine what happened even if you did get something which looks like success.
My app makes a lot of URL requests (calling a web service) in a loop for an extended period of time. When I watch the app in the Allocations tool, I see memory consumption going up continuously during the run of that loop. For testing purposes, I've reduced the loop to the following, which exhibits the same behavior:
NSURL *myUrl = [[NSURL alloc] initWithString:#"http://my.server.com/webservice"];
NSURLRequest request = [[NSURLRequest alloc] initWithURL:myUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
while (1)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *rawResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[pool release];
}
When I first discovered the problem, I figured it was because my URL requests/responses were being cached. That's when I added the [[NSURLCache sharedURLCache] removeAllCachedResponses]. I was expecting that to clear out the cache after each call to the web service and free up any memory being used for URL caching purposes. No luck.
I must be doing something wrong, but I can't find it. Am I barking up the wrong tree in thinking it's the URL caching? What else could it be?
I've written a short iOS application that contacts a server via the usual:
NSString *URLString = #"http://mysite/script.php?foo=1";
NSURL *getURL = [NSURL URLWithString:URLString];
NSURLRequest *getRequest = [NSURLRequest requestWithURL:getURL];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:getRequest returningResponse:&response error:&error];
This works OK, but as I access the web site relatively frequently with various values of foo, and as we plan to migrate to https, people have raised the question about the overhead on the phone of setting up and tearing down the NSURLConnection every time. I see this answer saying that the class handles caching, but is that true for https too?
According to my tests, yes, it does appear to be true that the NSURLConnection class does caching of https to some extent. I did like the look of AFHTTPClient as mentioned in the comment, but due to various issues I was unable to use it in this project, however.
I'm currently doing this when populating core data from a JSON file:
NSString *urlString = [value objectForKey:#"url"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *dataResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[managedObject setValue:dataResponse forKey:#"image"];
Is there a better (asynchronous) way to do this with AFNetworking? What is the best method for this case? Does it have to be synchronous because we're dealing with CoreData?
UPDATE: Trying this now:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
[managedObject setValue:data forKey:#"image"];
}];
For some reason when I access the managed object later, the image attribute is always null, even though *data above is not null in the completion handler. The image gets saved fine in the synchronous method. What am I missing?
NSURLConnection can deal with async too.
The method that you can use is (iOS >= 5) is
+ sendAsynchronousRequest:queue:completionHandler:
If you need to target iOS < 5 then use the delegate pattern for NSURLConnection. A good wrapper for this can be found in NSURLConnection and grand central dispatch.
About Core Data, I would say it depends. If data you need to store is cheap, do it in the main thread. On the contrary you have three different ways to do it:
(1) use new Core Data queue-based API (iOS >= 5)
(2) kick off a NSOperation within a NSOperationQueue and do the long work in background
(3) use GDC
Pay attention to Core Data constraints (threads constraints) when you deal with (2) or (3).
Hope that helps.
P.S. If you want to know something else let me know.
There's a sendAsynchronousRequest:queue:completionHandler: message of NSURLConnection.
I have a method which fetches data from server. I'm calling the method several times inside a loop. The problem is when i use instrument to check memory allocation, i have increase in live memory in each of the method call. BTW I am using ARC.
-(NSArray*)callService
{
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL: serviceURL];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:requestData];
[request setTimeoutInterval:30.0];
NSURLResponse *response = NULL;
NSError *requestError = NULL;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&requestError];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSArray *array = [(NSArray*) [responseString JSONValue] autorelease];
responseString=nil;
return array;
}
I have a method which fetches data from server. I'm calling the method
several times inside a loop.
Don't. Call one, wait for the response and call the next one.
Edit 1:
A synchronous load is built on top of the asynchronous
loading code made available by the class. The calling thread is
blocked while the asynchronous loading system performs the URL load on
a thread spawned specifically for this load request. No special
threading or run loop configuration is necessary in the calling thread
in order to perform a synchronous load.
Because this call can potentially take several minutes to
fail (particularly when using a cellular network in iOS), you should
never call this function from the main thread of a GUI application.
If you have zombies on (scheme settings) ensure you turn them off while testing in Instruments for memory usage. The default setting for NSDeallocateZombies is NO, so zombies won't be deleted at all. So either set NSZombiesEnabled to NO (or the equivalent scheme checkbox) or leave in on but set NSDeallocateZombies to YES.
The problem was with SBJSON parser. Since i'm using iOS 5, i used NSJSONSerialization and it works like a charm.
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: responseData options: NSJSONReadingMutableContainers error: &e];
instead of the JSONValue