How to keep a connection alive using ASIHTTP? I need to make multiple requests, but can't afford to open, then close a request connection - iphone

I have a method that I call to make web service requests using GET. It looks like this:
- (UIImage*)getImageWithSeriesGUID:(NSString*)seriesGUID ImageID:(int)imageID {
NSString * unescapedString = RIVERWOODS_GET_IMAGE(seriesGUID, imageID);
NSURL *url = [[NSURL alloc] initWithString:[unescapedString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setRequestMethod:#"GET"];
[request addRequestHeader:#"Connection" value:#"Keep-Alive"];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSData *response = [request responseData];
//NSLog(#"Size: %#",[response length]);
NSString *content = [[[NSString alloc] initWithData:response
encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"Data: %#", content);
UIImage *image = [UIImage imageWithData:response];
return image;
}
return nil;
}
This approach works ok, but it is just REALLY slowly. On the other end I am iterating through a for loop so this method gets called 20 times if the picture array I need to collect has 20 images. I am looking to improve the efficiency of this process, and I am thinking that I should be able to iterate through all the image Id's I need to collect right here in this method.
It seems to me that the reason this goes so slowly is because the multiple requests that we keep opening and closing. THe images I am pulling in are on average ~15kb.
My question: What can I do to change this method around to make it so I can improve efficiency and take advantage of the HTTP keep-alive features? I am thinking that instead of passing in an image ID, I can just pass in the count of the array I need to make, and then setup a for-loop of some sorts here in the request method which would then allow me to pass back an array of images...
Is that the way to go? Is there something I am missing? I appreciate your help and input!!

The reason why this is slow as hell is that you're doing the requests synchronously (which is always a no-no anyway), one-by-one. You need to refactor your download method to work asynchronously, and concurrently.
My approach to requesting data on the wire in that manner is as follows:
Create a global network connection 'controller' (accessible from your App Delegate), which can create an ASINetworkQueue on the fly when required and release it when no requests remain
Wrap your requests into a subclass of ASIHTTPRequest, and override the done/fail methods in those subclasses (make them fire a notification with returned data if you like; or write to disk and update a db with their reference).
For every request, grab the queue reference, and add your request to the queue.
The queue will grow and shrink as needed
If I were at my computer I'd check into github an example of this, but really the only difficult part is the global connection manager, and the ASI* guys have written a great example here on gist.github. Also, a better explanation of the above (where I learnt it from) is here.

Related

controlling order of received data in NSURLConnection?

I have an array of URLs which I use to receive associated Images from remote server using NSURLConnection
NSURL *url = [NSURL URLWithString:URLString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest
requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:2.0f];
// Run network request asynchronously
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
in the delegate protocol
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// As chuncks of the image are received, we build our data file
[self.imageData appendData:data];
}
and finally
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// All data has been downloaded, now we can set the image in the images array
_img = [UIImage imageWithData:self.imageData];
[self.imagesArray insertObject:_img atIndex:_index];
[self.imageData setLength:0];
_index = _index+ 1;
}
but the order of received is not always the same as the order of sending URL request I tried using an instance variable index to insert these images in the same order in the images array but that does not always work and I still am getting unordered listings , could anyone point me in the right direction as to how to achieve this.
What I've done in my own implementations is to create a custom Objective-C object which I insert into a mutable array (let's call it a "RemoteImageObject") which contains both the image data and the URL from which it came from.
What you would do is make the request, create a RemoteImageObject and add the URL to it, add that to your array.
When the request comes back with image data. Go look up your RemoteImageObject in your array and add the image data to it.
And when you want to display the image data, just fetch the imageData for each of those RemoteImageObjects.
Makes sense?
In general if I am downloading images I rely on the excellent AFNetworking classes.
http://afnetworking.com This provides the overall behavior you are looking for (one takes a UIImageView, and default image, another will just deliver the image bytes)
If there is a reason that you MUST make your own, then I would have a dictionary of download name to NSURLConnection. This will allow you to then figure out which download has ASYNCHRONOUSLY completed, and act correctly on it.
If you really mean a SYNCHRONOUS delivery of download objects, then dispatch them one at a time, wait for the response, and then start the next on completion.

Asynchronous network requests semi-crashing app

So whenever my ViewController is loaded I do an upload to a server. There seems to be a problem however. When there are a substantial number of asynchronous requests it can semi-crash the app. What I mean by that is that the requests just won't go through, and no other requests (which are on another thread) proceed. On top of that, the keyboard goes supremely laggy (weird I know). Anyway, so It's a serious issue considering the other network requests don't get sent because of it. What I find weird is that the upload requests are the same in number as the download requests (and the uploads don't even do anything, they just make a normal http request), yet the download requests work fine in any quantity. Here is my code:
- (void)serverUploadAll:(myEntity *)send
{
NSMutableString *urlString = [[NSMutableString alloc] initWithString:ServerApiURL];
NSString *addTargetUrl = [NSString stringWithFormat:#"/addtarget?deviceToken=%#&appVersion=%#&targetId=%#", postToServer.deviceToken, postToServer.appVersion, send.Id];
[urlString appendString:addTargetUrl];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:queueTwo completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
//NSLog(#"response=%#", response);
//NSLog(#"data=%#", data);
//NSLog(#"error=%#", error);
}];
}
The code above is called as a database is cycled through, with the problem arising when there are a substantial amount of calls, i.e. 120+. My download requests are actually done using AFNetworking, so perhaps that is why they work efficiently. Anyway, to summarise, why when the above code is called multiple times does it just jam, and stop?
Thanks for the help, really appreciated.
Regards,
Mike
UPDATE: So, thanks to the brilliant answer by runmad, I'm using the AFNetworking approach, however, it crashes with this error -[AFHTTPRequestOperation _propertyForKey:]: unrecognized selector sent to instance. I don't get why that isn't working, here is my code:
- (void)cycleThroughEntries
{
MyEntity *myEntity;
NSMutableArray *urlRequests;
urlRequests = [[NSMutableArray alloc] init];
for (id i in fetchedResultsController.fetchedObjects) {
myEntity = i;
NSMutableString *urlString = [[NSMutableString alloc] initWithString:ServerApiURL];
NSString *addTargetUrl = [NSString stringWithFormat:#"/addtarget?deviceToken=%#&appVersion=%#&targetId=%#", postToServer.deviceToken, postToServer.appVersion, myEntity.Id];
[urlString appendString:addTargetUrl];
//NSLog(#"URL sent is: %#", urlString);
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[urlRequests addObject:requestOperation];
}
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#""]];
[client enqueueBatchOfHTTPRequestOperationsWithRequests:urlRequests
progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%d / %d", numberOfCompletedOperations, totalNumberOfOperations);
}
completionBlock:^(NSArray *operations) {
NSLog(#"All Done!");
}];
}
Not entirely sure what's wrong, so would appreciate the help. Thanks.
UPDATE 2: Fixed. I shouldn't have been making an array of AFHTTPRequestOperations, but of just normal NSURLRequests. Problem solved!!
There is a limit to how many asynchronous connections you can make - even if they occur on background threads. 120+ network calls at the same time sounds a bit crazy, especially if they include uploads which take longer than a GET request.
I would suggest you consider rewriting what you need to request/upload to the server to reduce the amount of requests. You need to ensure that the calls don't all happen at the same time and you hold off on most calls until others are completed.
Enter NSOperationQueue...
I would create a manager class which handles all your requests. In this class you create an NSOperationQueue where you can keep adding requests to it. Here's a good tutorial. You can set the number of concurrent requests and the NSOperationQueue will ensure the next requests in the queue wait until the currently running requests are done. It does a lot of heavy lifting for you with networking.
You may also consider having a look at AFNetworking's NSOperationQueues to queue up all your network calls so they don't all occur at the same time.
In AFNetworking's AFHTTPClient You may be able to use the following method depending on how you're setting up your requests:
- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)urlRequests
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock;
That should get you started :) Good luck.
UPDATE
You can also add operations to the AFNetworking operationsQueue if you want. You just have to make sure to start it if it's not currently running already. This way you're able to add additional requests from other parts of the app at various times. You can always check if any operations are running/in queue and the awesome thing is that is allows you to easily cancel any running operations. It can be found here.

No request information sent with ASIHTTPRequest

I am experiencing an issue on iOS 4.3+ with ASIHTTPRequest where a request is fired but no data (Request methed, url, headers, etc) reaches the server. The connection times out because it never hears back.
The server hears the empty request (after some delay), then hears a valid request which is of course never reported to higher level code because the connection has timed out. This is all kind of strange because the request was not configured to resend data.
Often this happens after the app has been pushed to the background for some time (15 min or more) and the phone has been allowed to sleep.
My configuration of the request is as follows:
NSMutableData *postData = nil;
NSString *urlString = [NSString stringWithFormat:#"%#%#",[self baseURL],requestPath];
OTSHTTPRequest *request = [OTSHTTPRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setCachePolicy:ASIFallbackToCacheIfLoadFailsCachePolicy];
[request setTimeOutSeconds:45];
//Set up body
NSString *queryString = [self _RPcreateQueryString:query];
if ([queryString length]>0) {
if(method == RPAsyncServerMethodPost || method == RPAsyncServerMethodPut){
postData = [[[queryString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES] mutableCopy] autorelease];
}else{
NSURL *url = [NSURL URLWithString:[urlString stringByAppendingFormat:#"?%#",queryString]];
[request setURL:url];
if (!url) return nil; //url String malformed.
}
}
// ... ///
// method setting stripped for brevity
[request addRequestHeader:#"Content-Type" value:#"application/x-www-form-urlencoded"];
if(headers){
for (NSString* head in headers) {
if ([[headers valueForKey:head] isKindOfClass:[NSString class]])
[request addRequestHeader:head value:[headers valueForKey:head]];
}
}
[request addRequestHeader:#"Content-Length" value:postLength];
[request setPostBody:postData];
OTSHTTPRequest is simply a subclass of ASIHTTPRequest that contains properties for a string tag, a pretty description, and other bling for use by consuming objects and does not override any ASI stuff.
Can anyone shed a light on why/how ASI could open a connection and then send absolutely nothing for minutes at a time?
Edit: Just to clarify. The connections DO make contact with the server, it just never sends any data through the connection from what my server logs can tell. This seems to always happen on app wake and effects all connections including NSURLConnections spawned by MapKit. the whole app just seems to loose its marbles.
I also see a bunch of background tasks ending badly right before this, but i can never catch them while in the debugger.
It doesn't look like you are starting your request based on the code that you have provided. Try call the -[startSynchronous] or -[startAsynchronous] methods of your OTSHTTPRequest object after you are done setting its various properties.
Are you setting the delegate, either I overlooked it or you stripped it out.
I didnt want to say anything till a few days passed with out the issue. The solution in this case was very obscure.
It appears the version of TestFlight i was using has a bug in it that may have contributed to this issue. Since its removal i have not experienced the issue.

How to POST multiple requests with XML parser?

I want to POST 3 requests within same class with XML parser. I can manage to do only one request at a time. When I POST multiple requests, it says Parser Error. This is how I tried.
NSURL *url = [[NSURL alloc] initWithString:getAllFoodsURL];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
NSString *paramDataString = [NSString stringWithString:
#"<GetNames><DeviceId>1234</DeviceId><UserId>200</UserId></GetNames>"];
[req addValue:#"application/xml" forHTTPHeaderField:#"content-type"];
[req setHTTPMethod:#"POST"];
NSData *paramData = [paramDataString dataUsingEncoding:NSUTF8StringEncoding];
[req setHTTPBody: paramData];
NSURLConnection *theConnection=[[NSURLConnection alloc]initWithRequest:req delegate:self];
if (theConnection) {
NSMutableData *data = [[NSMutableData alloc] init];
self.receivedData=data;
[data release];
}
I have used NSXMLParser delegates methods. After one request is completed(connection release), then I create another connection and do the same process for second request.
But it doesnot work.
I want to know, how to manage multiple requests with NSXMLParser?
If you can give me a code example, its highly appreciated.
I guess NSThread is what you are looking for.
But I am not sure as I am not much aware about that.
Note that NSXMLParser isn't involved in HTTP requests -- do you mean NSURLRequest instead? You'll need to make your requests separately, possibly using separate blocks, operations, or threads.
Once you've retrieved the data for each request, you'll need to use separate NSXMLParser objects for each. A single instance of NSXMLParser is tied to its XML data at initialization -- you can't reuse a parser. You can use the same delegate for all your xml parsers, and the delegate can use the first parameter to each of the xml parser delegate methods (i.e. parser) to know which parser is calling a given method.

iOS Handling Multiple Asynchronous Requests: Send a Signal When All Requests Are Finished

So I am browsing over Stack Overflow for ways to handle asynchronous requests effectively. Right now I am using ASIHTTPRequest and my application consumes a REST API, in which a request to a single resource prompts me to request 5 or more additional resources (linked resources).
Right now I am doing all asynchronous request inside one huge method, there may be around 6 asynchronous request each with their setCompletionBlock and setFailBlock. If you have used ASIHTTPRequest, you must have an idea of how much repetitive code that will take.
Anyway, I seem to have found a solution to determine if all requests are finished with this answer:
Multiple asynchronous URL requests
however, I don't know how to apply that answer in code ,as I have not used push notifications before and I have always used "self" as delegats and never other classes.
Any ideas how I can apply the answer to code or better yet, do you know of any other methods?
Thanks in advance!
Put all the requests into an ASINetworkQueue: http://allseeing-i.com/ASIHTTPRequest/How-to-use#about_ASINetworkQueues.
ASINetworkQueue has a callback called "queueDidFinishSelector".
Use a GCD group with dispatch_group_wait.
Example:
__block NSMutableArray *imageURLList = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (Helper *siteURL in list) {
dispatch_group_async(group, queue, ^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:siteURL]];
NSURLResponse *response;
NSError *error;
NSData *urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (urlData.length)
[imageURLList urlData];
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
Note that the block is executed async so it is alright to use sendSynchronousRequest:.