How can I add HTTP request caching to an application using ASIHTTPRequests? - iphone

I'm using ASIHttpRequests and an ASINetworkQueue in an iphone app to retrieve some 100k XML files and a lot of thumbnails from a web service. I'd like to cache the requests in the style of NSURLCache. ASI doesn't seem to support caching as is, and I looked at the code and it drops to C to create the requests, so inserting the NSURLCache layer seemed tricky.
What's the best way to implement this?

ASIHTTPRequest now supports caching - check out ASIDownloadCache ie.
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]]

You could provide your own caching before dropping down into ASI code.
Wrap your ASI code in a class that has a method:
-(NSString *)getContentFor:(NSURL *)url
That method first checks an internal NSDictionary to see if it has a key present for the specified url. If it does, it returns the object stored with the key.
If it doesn't, it performs the normal ASIRequest. When the request is received from the server, it stores it as a string in your dictionary with the key of the url.
Of course, you'll need to handle asynchronous requests and expiring of old requests with care.

Anyone asking how they can do this with ASIHTTPRequest directly may be interested in this branch of the code that adds support for this feature as an option.

NSURLConnection has support for caching in the style of NSURLCache, and it does a lot of work for you behind the scenes. It even has a nice delegate method that will allow you to manipulate the cachedResponse:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse

try this, it works for me.
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy];
[request setSecondsToCache:60*60*24]; // Cache for 24 hrs
[request setDelegate:self]; // A delegate must be specified
[request setCompletionBlock:^{

Related

NSURLConnection Hanging

I have an iOS 5 app that uses NSURLConnection to load some XML via GET. On very rare occasions connections appear to get stuck in a condition where they timeout repeatedly.
An example request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:url]];
/*
The request is set with a timeout interval of 10 because (due to the nature of
the app and the XML feed) this data is reloaded every 15 seconds.
*/
[request setTimeoutInterval:10];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setHTTPMethod:#"GET"];
self.afOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
self.afOperation.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
self.afOperation.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
//snip success/completion block code
[self.afOperation start];
So far I've seen three "recovery" scenarios when the requests begin to hang.
Quit the entire app
Plug the device into a computer (yes, really). Right after the iPhone/iPad acknowledges the connection it will immediately stop timing out.
Leave the app and go do something else for awhile. Quickly leaving and reentering the app is typically insufficient to cause recovery.
As you might imagine, I find this incredibly bizarre. At this time I've replaced my own NSURLConnectionDelegate implementation with AFNetworking (as seen above) and am still running into the same problem. I've added logging to every NSURLConnectionDelegate protocol selector and found that the only selector called (after calling start) is connection:didFailWithError:. I've ensured I'm not piling up multiple requests (the previous request is always canceled and nil'd before starting a new one). Additionally, I've verified that no request is actually being sent via tcpdump on my router. What could cause this type of behavior?
It turns out this problem is caused by the TestFlight SDK v1.0 and below. See Why does NSURLConnection fail to reach the backend?
Until they release a fix there's no way to workaround the problem short of stripping out the SDK entirely.
I started seeing the error after installing the testflight sdk, and removing it helped me get rid of it. However, I think it's caused by the interaction between Testflight and ASIHttpRequest (or whichever rest kit you use). It can also be possibly resolved by the following the solution in the link below (disabling compiler optimization on your ASIHttpRequest and ASIFormDataRequest files in your build phases)
https://groups.google.com/forum/?fromgroups#!topic/asihttprequest/fw7PDcD2wKI%5B1-25%5D

Getting Request Timed Out - ASIHTTPRequest - code provided

Most of the time I end up in failMethod, I get the message Request timed out and a whole lot of jargon in the error. Why is this? Is there a solution for it?
I am using ASIHTTPRequest
[self setNetworkQueue:[ASINetworkQueue queue]];
[[self networkQueue] setDelegate:self];
[[self networkQueue] setShouldCancelAllRequestsOnFailure:NO];
[[self networkQueue] setRequestDidFinishSelector:#selector(successMethod:)];
[[self networkQueue] setRequestDidFailSelector:#selector(failMethod:)];
ASIHTTPRequest *r = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:#"http://www.example.com/wenb"]];
[r setUseKeychainPersistence:YES];
[[self networkQueue] addOperation:r];
[[self networkQueue] go];
Request timed out generally means what it says: that the server you're trying to get data from took too long to respond, and your request was terminated (I assume the URL you've posted isn't the URL you're trying to reach).
There are two solutions: preferably, figure out why your requests are taking so long to complete, because ASIHTTPRequest has a 10 second time out as default, which is quite long. The other, easier, option is to increase the timeout to, say, 30 seconds, which you can do like this:
[ASIHTTPRequest setDefaultTimeOutSeconds:30]
Perhaps try running your requests through something other than the phone to see how long they take, and to get more visibility on what the problem is.
For me this is depends on the mobile carriers. I got the same issue and I have tested on different carrier network using same application. It seems to me the network providers are using different type of connectivity (proxies). When you look at your phone logs, you can see same error from you mail client too. The one solution is, use the simple NSURLConnection with HTTP methods.

Parallel downloads in iOS

I am working on building a download manager functionality for the app I am working on. As a requirement we need to support maximum of three parallel downloads. I saw some code examples of this forum using same delegate object and create multiple instances of NSURLConnection objects. A drawback (that I think, and I may be wrong) to this approach is, all the callbacks to delegate object would happen on the same thread. This would result in packets being queued up on the thread. Am I missing something here.
Is there any other way of implementing this functionality such as do a NSInvocationQueue and start individual download on a different thread and thus get better efficiency. With this approach it adds a lot of complexity for tracking progress for each download, pause/resume downloads and thread management.
I am planning to create asynchronous requests on individual thread and not keep synchronous connections for obvious reasons. Also I am downloading large video files > 100 MB and storing it directly to a file. I am a little unclear as to how packets would be queued up and would I run out or memory or would it make main thread unresponsive.
Any pointers or help is greatly appreciated.
Thanks
As Tommy has pointed out, using separate threads just for downloading data is generally not very efficient. It also has a higher memory overhead than using the asynchronous interface of NSURLConnection on the main thread and you lose a lot of control (you can't cancel synchronous connections running on a background thread).
You're right, the delegate callbacks will all be queued on the main thread, but usually, the only thing you do there is to concatenate the data chunks until your download is finished – this is computationally very cheap.
If you intend to do computationally expensive things with the data after it's downloaded (like creating thumbnail images, parsing etc.), you could easily dispatch that work to a GCD queue after your connection has finished downloading. That way, you don't lose control over the download process, can easily display progress or cancel running downloads, but still don't block the main thread.
Apple's recommendation is that you use NSURLConnection asynchronously with a delegate rather than using threads and a blocking connection. If you use multiple NSURLConnections on the same thread then the data will all be returned on the same thread, but that isn't a problem. The data doesn't somehow become intertwined unless your code intertwines it, and there will be no effect on network performance.
Launching threads simply for the process of fetching data is less efficient, especially in battery utilisation, than simply using multiple NSURLConnections on the same thread.
It will help -
http://allseeing-i.com/ASIHTTPRequest/
For parallel download you can use ASINetworkQueue.
You shall look into HTTP Client library like ASIHTTPRequest.
ASIHTTPRequest handle concurrent requests using a queue, which you can limit the concurrency easily. Example code borrowed from ASIHttpRequest:
- (IBAction)grabURLInTheBackground:(id)sender
{
if (![self queue]) {
[self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[[self queue] addOperation:request]; //queue is an NSOperationQueue
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
Modify the [NSOperationQueue maxConcurrentOperationCount] to change concurrency.

How to tell iPhone NSURLConnection to abort

I have an app that makes moderate use of NSURLConnection. These async calls eventually finish and release properly (it looks like), but sometimes it takes some time for them to finish.
So, there are times when I exit the app, (note, not just sending it the background), that some of these connections are still active. If I immediately restart the app, the app freezes on startup. (didFinishLaunchingWithOptions never seems to get called).
While I'm not certain these connections are the issue, it would probably be good to terminate or cancel any remaining. Any suggestions on how to do this?
Bonus points on how to debug the restart also. (I'm already saving NSLog statements to a downloadable file)
You can cancel any NSURLConnection by sending it a cancel command.
[connection cancel];
From Apple docs
Cancels an asynchronous load of a
request. Once this method is called,
the receiver’s delegate will no longer
receive any messages for this
NSURLConnection.
Your start up issue could be related but hard to tell without knowing what type of data you're downloading and how you are using it.
Make sure you are calling release on the connection. Maybe it doesn't call shutdown or close on the socket until the last reference is dropped. I do something like this with no issues with references.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
For cancel last request : [NSURLConnection cancelPreviousPerformRequestsWithTarget:self];
For cancel any specific request : [request cancel];
Note: Here request is the instance of NSURLConnection

Objective-C: server requests in a thread (like AsyncTask in Android)

I would like to start a server request, you can cancel.
My idea is to start the request in a thread so that the user interface does not freeze. So you can kill the whole thread including the request with a click on a "Cancel"-button.
With Android it works: the server request gets started in a "AsyncTask" and in the "onReturn()"-method I can react as soon as the server request finish.
How can I implement this using Objective-C on iOS?
My first attempt was a "NSInvocationOperation". You can cancel the operation, but it's difficult to handle when a request is completed and results are available. I think NSInvocationOperation is not the solution for my issue.
The would you recommend to me? Is NSThread the right choice for me?
Thank you very much!
Note!
This extremely old answer is now here only for historic purposes.
The wonderful ASIHttpRequest library no longer exists; technology is totally different now.
It is unbelievably simple to do this with ASIHttpRequest.
(Asynchronous is so simple, there is no reason you would ever do it not-asynchronously.)
Here are some rough extracts that might get you started.
...
ASIFormDataRequest *request;
...
NSURL *url = [NSURL URLWithString:#"https://blah.blah/blah.cgi?blah"];
request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"fred" forKey:#"username"];
[request setPostValue:#"flint" forKey:#"passie"];
[request setPostValue:#"stone" forKey:#"town"];
// send up data...
[request setData:[NSData dataWithBytes:blah length:blah] forKey:#"thefile"];
// or perhaps something like...
[request setData:imageData withFileName:#"blah.png"
andContentType:#"image/jpeg" forKey:#"photoimage"];
[request setDelegate:self];
[request setDidFinishSelector:#selector(postingDone:)];
[request setDidFailSelector:#selector(postingDoneProblem:)];
[request startAsynchronous];
...
-(void) postingDone:(ASIHTTPRequest *)request
{
// it worked
}
-(void) postingDoneProblem:(ASIHTTPRequest *)request
{
// failed
}
Couldn't really be any easier. You're basically just typing out the fields and values.
Per your question, here is how you cancel an "in-flight" request... just set the delegate to nil and then "cancel" it.
[myRequest setDelegate:nil];
[myRequest cancel];
[myRequest release];
ASIHttpRequest is the "miracle library". If you are new to iOS, ASIHttpRequest is simply THE most used 3rd party library. Essentially, every single iPhone app of the 300,000 iPhone apps uses it.
If at all possible BE SURE to donate a few bucks to the guy -- if he stops supporting that library, 100,000 iPhone programmers are buggered!
the documentation is trivial, a child can follow it:
http://allseeing-i.com/ASIHTTPRequest/How-to-use
"Creating an asynchronous request"
it is probably - almost certainly - the most amazingly simple networking library on any platform. It is trivial to do what you describe, happily. Enjoy.
NSURLConnection is async by default and supports cancelation as well as delegate methods when connection has been established, data has been received or whole request has been completed.
Also data transfer takes place in background so that UI stays responsive.
Cocoa's built-in async networking code is not thread-based but works with run loop events, but the result (asynchronous connections) is the same.
Create an NSURLConnection with +[NSURLConnection connectionWithRequest:delegate:]. The delegate you set will be informed about the progress of the connection and can cancel it anytime with -[NSURLConnection cancel].
Check out ASIHTTPRequest, specifically, the ASINetworkQueue subclass, which is described as:
ASINetworkQueue
A subclass of NSOperationQueue that
may be used to track progress across
multiple requests.
I've only used ASIHTTPRequest for a single synchronous request to download directly to disk, which was easy to implement, but I've heard good reports of using queues to manage multiple asynchronous server requests at once.
One thing to note on the recommendations to use +[NSURLConnection connectionWithRequest:delegate:] is that it should only be called from the main thread as of iOS 4.
see http://blog.mugunthkumar.com/coding/ios4-issue-nsurlconnection-and-nsoperation/ for an example of how to deal with this.