nsurlconnection asynchronous request - iphone

First of all the questions are failry simiple.. if you just want to see what they are skip to the bottom of this post and you will see them in bold.. for more detail then you can read the rest of this post...
I am just trying to iron out my NSURLConnection so that its working smoothly and I understand this properly. There is a profound lack of example/tutorials for Asynchronous connections on the internet or not any that I can find that explaine what is going on with any level of depth other than getting the connection up and running which after working on it seems pretty simple. Hopefully this question can full the void that I feel is out there for other users.
So, in my .h file i have imported the foundations headers and declared the methods required for the received or lack of received data (errors etc).
.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> //add foundations
//.. other headers can be imported here
#interface MyViewController: UITableViewController {
//Im not setting any delegates to access the methods because Is all happening in the same
//place so I just use the key word 'self' when accessing the methods declared below
//I'm not sure if this is the best thing to do but I wasn't able to get my head around declaring the delegate or how it would help me with the way I have set up my request etc.
}
- (IBAction)setRequestString:(NSString *)string; //this method sets the request and connection methods
//these methods receive the response from my async nsurlconnection
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
So thats my header file.. pretty simple not much explaining needed.
.m
//call setRequestString from some other method attached to a button click or something
[self setRequestString:#"rss.xml"];
//..
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http:www.comicbookresources/feeds/"]; // address not real jsut example
//append the string coming in to the end of the databaseURL
[databaseURL appendString:string];
//prepare NSURL with newly created string
NSURL *url = [NSURL URLWithString:databaseURL];
//AsynchronousRequest to grab the data
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
[self receivedData:data];
}else if ([data length] == 0 && error == nil){
[self emptyReply];
}else if (error != nil && error.code == NSURLErrorTimedOut){ //used this NSURLErrorTimedOut from foundation error responses
[self timedOut];
}else if (error != nil){
[self downloadError:error];
}
}];
}
now set up the methods that were initialized in the .h file and called in the if statement above
- (void)receivedData:(NSData *)data
{
NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", newStr); //logs recived data
//now you can see what data is coming in on your log
//do what you want with your data here (i.e. start parsing methods
}
- (void)emptyReply
{
//not sure what to do here yet?
}
- (void)timedOut
{
//also not sure what to do here yet?
}
- (void)downloadError:(NSError *)error
{
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
Cool so that pretty much the basics of what I have done right there.. now the questions I have are as follows.
Question one:
Where I call NSURLConnection like so
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
What is happening here what is the ^ for is that executing that whole block (including the if statements) on a different thread or something? because it looks alot like grand central dispatch formatting but slightly different.
Question two:
what should I be doing inside emptyReply & timedOut methods?
Question three:
How would I incorporate caching into this? I would like to cache the responses I get back from different requests. i.e. with my setRequestString you will see there is a string input parameter, so i can request different rss feeds with the same method.. I need to figure out how to cache these responses into individual caches.. but im not sure where to start with it.
Finally
If you have made it this far, thank you very much for reading my question. Hopefully with your responses we can get a pretty nice solution going here.. that other people can use for themselves and pick and choose the bits and peices they need that works for there own solution..
Anyway thank you very much for reading and I look forward to your replies.. even if they are just refrences to tutorials or examples you think might help me.. anything is good I just want to fully understand whats going on and whats a good solution.

Read about blocks in Apple documentation. Its new. Or you can read here
You can show errors such as request timed out etc. You don't really have to handle them separately than the error one unless you have special logic.
Try this for caching
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];

Related

How to pass info with NSURLConnection instance so I can get it from connectionDidFinishLoading

I am using NSURLConnection to load data from a response. It works as it should, the delegate method connectionDidFinishLoading has the connection instance with the data I need. The problem is that I want to pass some information along with the request so that I can get it when the connection finishes loading:
User wants to share the content of a URL via (Facebook, Twitter,
C, D).
NSURLConnection is used to get the content of the URL
Once I have the content, I use the SL framework
SLComposeViewController:composeViewControllerForServiceType and need
to give it the service type
At this point I don't know what service the user selected in step 1. I'd like to send that with the NSURLConnection.
Can I extend NSURLConnection with a property for this? That seems very heavy-handed. There must be a "right way" to do this.
Many Thanks
Assuming you don't need the delegate-based version of the NSURLConnection process for some other reason, this is a good use case for the block-based version:
- (void)shareContentAtURL:(NSURL *)shareURL viaService:(NSString *)service
{
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:shareURL];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] == 0 && error == nil) {
// handle empty response
} else if (error != nil) {
// handle error
} else {
// back to the main thread for UI stuff
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do whatever you do to get something you want to post from the url content
NSString *postText = [self postTextFromData:data];
// present the compose view
SLComposeViewController *vc = [SLComposeViewController composeViewControllerForServiceType:service];
[vc setInitialText:postText];
[self presentViewController:vc animated:YES];
}];
}
}];
}
Since blocks can capture variables from their surrounding scope, you can just use whatever context you already had for the user's choice of service inside the NSURLConnection's completion block.
If you're still wed to the delegate-based NSURLConnection API for whatever reason, you can always use an ivar or some other piece of state attached to whatever object is handling this process: set self.serviceType or some such when the user chooses a service, then refer back to it once you get your content from the NSURLConnectionDelegate methods and are ready to show a compose view.
You could check the URL property of an NSURLConnection instance and determine the service by parsing the baseURL or absoluteString property of the URL with something like - (ServiceType)serviceTypeForURL:(NSURL *)theURL;
All the NSURLConnectionDelegate methods pass the calling NSURLConnection object-so you could get it from
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
or
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

How to write a dowloader class for updating download progress in iOs

Here is my actual issue, as some suggested I want to write a class for handling the multiple download progress in a UITableView. I have no idea how to write a class for this, can somebody help with some tips or ideas?
The group to look at is NSURLRequest and NSURLConnection. The former let's you specify the request (URL, http method, params, etc) and the latter runs it.
Since you want to update status as it goes (I answered a sketch of this in your other question, I think), you'll need to implement the NSURLConnectionDelegate protocol that hands over chunks of data as it arrives from the connection. If you know how much data to expect, you can use the amount received to calculate a downloadProgress float as I suggested earlier:
float downloadProgress = [responseData length] / bytesExpected;
Here's some nice looking example code in SO. You can extend for multiple connections like this...
MyLoader.m
#interface MyLoader ()
#property (strong, nonatomic) NSMutableDictionary *connections;
#end
#implementation MyLoader
#synthesize connections=_connections; // add a lazy initializer for this, not shown
// make it a singleton
+ (MyLoader *)sharedInstance {
#synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[MyLoader alloc] init];
}
}
return _sharedInstance;
}
// you can add a friendlier one that builds the request given a URL, etc.
- (void)startConnectionWithRequest:(NSURLRequest *)request {
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *responseData = [[NSMutableData alloc] init];
[self.connections setObject:responseData forKey:connection];
}
// now all the delegate methods can be of this form. just like the typical, except they begin with a lookup of the connection and it's associated state
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responseData = [self.connections objectForKey:connection];
[responseData appendData:data];
// to help you with the UI question you asked earlier, this is where
// you can announce that download progress is being made
NSNumber *bytesSoFar = [NSNumber numberWithInt:[responseData length]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[connection URL], #"url", bytesSoFar, #"bytesSoFar", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDownloaderDidRecieveData"
object:self userInfo:userInfo];
// the url should let you match this connection to the database object in
// your view controller. if not, you could pass that db object in when you
// start the connection, hang onto it (in the connections dictionary) and
// provide it in userInfo when you post progress
}
I wrote this library to do exactly that. You can checkout the implementation in the github repo.

check which request is which from NSURLConnection delegate

What is the best way to check which request is which inside the delegate method:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
}
Right now I have a NSURLConnection that I set to the NSURLConnection before making a request and inside didReceiveResponse I do:
if (self.tempConnection == connection)
however there is a possiblity this won't work for race conditions. Is there a better way to do this?
There is a better way in OS5. Forget about all those bothersome delegate messages. Let the connection build the data for you, and put your finished code right in line with your start code:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.site.com"]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"got response %d, data = %#, error = %#", [httpResponse statusCode], data, error);
}];
I've looked at a bunch of different ways to do this, and I've found that by far the cleanest and easiest in order to manage is to use a block pattern. That way you are guaranteed to be responding to the right request upon completion, avoid race conditions, and you don't have any issues with variables or objects going out of scope during the asynchronous call. It's also a lot easier to read/maintain your code.
Both ASIHTTPRequest and AFNetworking APIs provide a block pattern (however ASI is no longer supported so best to go with AFNetworking for new stuff). If you don't want to use one of these libraries, but want to do it yourself, you can download the source for AFNetworking and review their implementation. However, that seems like a lot of extra work for little value.
Consider creating a separate class to serve as the delegate. Then, for each NSURLConnection spawned, instantiate a new instance of the delegate class to for that NSURLConnection
Here's some brief code to illustrate this:
#interface ConnectionDelegate : NSObject <NSURLConnectionDelegate>
...then implement the methods in the .m file
Now, I'm guessing you probably have the code you posted in a UIViewController subclass (or some other class serving different purposes)?
Wherever you are kicking off the requests, use this code:
ConnectionDelegate *newDelegate = [[ConnectionDelegate alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
//then you can repeat this for every new request you need to make
//and a different delegate will handle this
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
// ...continue as many times as you'd like
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
You might consider storing all the delegate objects in a NSDictionary or some other data structure to keep track of them. I'd consider using an NSNotification in connectionDidFinishLoading to post a notification that the connection is done, and to serve whatever object created from the response. Lemme know if you want code to help you visualize that. Hope this helps!

How to return data gotten from a web service in objective- c (iPhone)?

This might be a dumb question. Sorry if it is.
But Im working on a project that consumes web services. I can connect to the web service and get the data I need fine.
I would like to have a method that returns this data obtained from the web service to the caller. The only problem is that the data is only obtained inside the ConnectionDidFinishLoading method, and I can't access this data from my method.
here is my code, that works fine:
- (NSData *) dataForMethod:(NSString *)webMethod withPostString:(NSString *)postString
{
NSURL *url = [NSURL URLWithString:[SigameWebServiceAddress stringByAppendingFormat:#"%#%#", #"/", webMethod]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [postString length]];
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [postString dataUsingEncoding:NSUTF8StringEncoding]];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn)
{
webData = [NSMutableData data];
}
// I WOULD LIKE TO RETURN WEBDATA TO THE CALLER HERE, BUT WEBDATA IS EMPTY NOW, THE
//connectionDidFinishLoading ONLY GETS CALLED WITH THE DATA I WANT AFTER THE COMPILER
//IS DONE EXECUTING MY METHOD.
}
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
[webData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[webData appendData:data];
}
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
NSLog(#"FATAL ERROR");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSLog(#"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(#"%#", theXML); //NOW, THIS IS THE DATA I WANT. BUT HOW CAN I RETURN THIS TO
//THE CALLER. I MEAN, THE CALLER THAT CALLED MY METHOD
//+ (NSData *) dataForMethod: withPostString:
}
Any help here is appreciated!
Thanks
There are really two ways to go about this.
Create a delegate interface
Use Blocks
I would strongly advise against using the synchronous methods - unless you are/have created your own asynchronous framework around them (i.e. you are manually starting another thread and executing your synchronous request on that thread). In the long run you will realize you need the requests to be async, and you'll have to re-work everything such that they are.
To give a quick overview of the two options I gave:
1. Create a delegate interface
The idea here is to create a class which performs the request, and create a protocol the caller must implement. When the request is complete, you will invoke a specified method on the delegate with the data:
The protocol might look something like this:
#protocol RequestClassDelegate <NSObject>
- (void)requestCompleted:(ResponseClass *)data;
- (void)requestError:(NSError *)error;
#end
The class which makes the request might look something like this:
#interface RequestClass : NSObject
- (void)makeRequest:(id<RequestClassDelegate>)delegate;
#end
And the request class implementation might contain some of the following, in addition to your connection logic:
#implementation RequestClass
{
__weak id<RequestClassDelegate> _delegate;
}
// Connection Logic, etc.
- (void)makeRequest:(id<RequestClassDelegate>)delegate
{
_delegate = delegate;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
[_delegate requestCompleted:theResult];
}
#end
2. Use Blocks
This solution is much the same as the first solution - but, a bit more elegant in my opinion. Here, we'll change the RequestClass to use blocks instead of a delegate:
typedef void (^requestCompletedBlock)(id);
typedef void (^requestErrorBlock)(NSError *);
#interface RequestClass : NSObject
#property (nonatomic, copy) requestCompletedBlock completed;
#property (nonatomic, copy) requestErrorBlock errored;
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error;
#end
And the implementation of that might look something like this:
#implementation RequestClass
#synthesize completed = _completed;
#synthesize errored = _errored;
// Connection Logic, etc.
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error
{
self.completed = completed;
self.errored = error;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
self.completed(theResult);
}
#end
It sounds like you are trying to use return the data synchronously from your method, but you are using an asynchronous method (using an NSURLConnection and presumably calling its start method) to begin retrieving data. If you really want your method to return its result synchronously, read on. As #Steve says in another answer, however, you may also reconsider your interface design and instead implement it using an asynchronous approach and use his recommendations for either a delegate or block-based interface.
If you want to return the data synchronously from your method, use a synchronous request. So change this part of your code:
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
[conn start]; // I presume you have this somewhere
if (conn)
{
webData = [NSMutableData data];
}
with something more like this:
NSURLResponse *response = nil;
NSError *error = nil;
webdata = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if (webdata) {
return webdata;
}
else {
// Handle error by looking at response and/or error values
return nil;
}
You will no longer need any of your delegate code if you use this approach. You will be limited in some ways though. For example, if your web service requires authentication via something other than URL parameters you can't use this approach.
Steve's answer is great and I can only suggest the way using blocks. Actually, as I am new into Objective-C I implemented the approach steve outlined. It works perfectly.
The Post for more details and my own point of view you can find here:
http://kerkermeister.net/how-to-build-an-cocos2d-ios-app-communicating-with-a-restful-api-the-sequence/
The Post contains all the tiny steps you need to follow to get Steve's solution approach with blocks working. That includes:
- an updateable view that will render information as soon as retrieved from Web API asynchronously
- a controller invoking the HTTP request to the Web API
- the actual HttpRequest class that uses iOS standard NSURLConnections
- a model class that uses blocks as callbacks to update its data
Your going to have to either implement a separate method in which you use the data once the data has been returned by the connectionDidFinishLoading method or make the request synchronously. The reason I believe the above does not work is because the request is happening on a separate thread, so the main thread continues, but does not actually have the data.
This is a good way to do that if synchronous is what you want:
Does all NSURLConnections connect asynchronously? iOs
In order to download data from webserivce - use NSURLSession -
A URL session task that returns downloaded data directly to the app in memory.
// 1. create NSURL link to your webservice
NSString *dataUrl = #"DATA_LINK_TO_WEBSERVICE";
NSURL *url = [NSURL URLWithString:dataUrl];
// 2. create a NSURLSessionDataTask
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Handle response here
}];
// 3.resume the task
[downloadTask resume];
Refernces:
apple documentation refrence:
https://developer.apple.com/documentation/foundation/nsurlsessiondatatask?language=objc
Raywanderlich great cookbook:
https://www.raywenderlich.com/2392-cookbook-using-nsurlsession
Your going to need to parse the XML that comes back. There are some good Objective C XML parsers out there. One in particular is made for ease of use....
http://nfarina.com/post/2843708636/a-lightweight-xml-parser-for-ios
It's a very light weight parser for extracting the values you want from XML. I've used many times with great success and little hassle. Here is how I query a web address and turn it into data.
NSString *query = [NSString stringWithFormat:#"http://WEB_ADDRESS_FOR_XML];
NSURL *URL = [NSURL URLWithString:query];
NSData *data = [NSData dataWithContentsOfURL:URL];
Or with NSURLConnection, in the did receive data:
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
//USE THE DATA RETURNED HERE....
}
Then use the Parser from my link to get the contents:
SMXMLDocument *document = [SMXMLDocument documentWithData:data error:NULL];
NSLog("\nXML Returned:%#",document);

Using NSUrlConnection but getting beat by race conditions

I have a program that has multiple url request so I used the the code in http://snippets.aktagon.com/snippets/350-How-to-make-asynchronous-HTTP-requests-with-NSURLConnection and put it in it's own class (class B).To call the class I am simple initializing class B in class A, sending a url to class B's get method([classname get:url]) and then getting the server response upon return.
The problem is that I am getting defeated by race conditions due to the fact that the didReceiveData: method is not complete by the time my method is returned.
I have gone through the developer example of using NSUrlConnection and they are updating views once the response finally came in so they didn't have to fight this problem.
Thank you so much for your help.
I need to keep the calls asynchronous due to the number of them I have to make but I am open to any suggestions.
Edit (moved from answer)
I changed the code to GCD based off of a tutorial and I am still getting defeated by the race condition. Here is the code that I am using now:
I changed it to GCS based on on your suggestion but I am still getting caught by the race condition. Below is the code that I changed it to and I am calling it by:
NSString *responseStringClassA = [InitalizedInstanceOfClassA LogIn:#"username" #"password"];
//Log into the server
-(NSString *)logIn: (NSString *) username password:(NSString *) password
{
NSString* returnString;
dispatch_queue_t downloadQueue = dispatch_queue_create("Login", NULL);
dispatch_async(downloadQueue, ^{
BOOL success = YES;
NSString *urlAsString =[NSString stringWithFormat:#""URL HERE];
NSLog(#"url sent out: %#", urlAsString);
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSError *error = nil;
NSData *connectionData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:&error];
NSLog(#"Connection Data: %#", [[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding]);
[returnString isEqualToString:[NSString stringWithUTF8String:[connectionData bytes]]];
if ([connectionData length] > 0 && error == nil) {
//success
success = YES;
}
else if([connectionData length] == 0 && error == nil){
//nodata
success = YES;
}
else if(error != nil){
//error ..
success = NO;
}
dispatch_async(dispatch_get_main_queue(), ^{
[returnString isEqualToString:[[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding] ];
});
});
return returnString;
}
It's the very purpose of asynchronous requests that the intial method returns almost immediately without having done the work. Later, when the work has been done, you will be notified and you can access and use the result.
But obviously, you're looking for something else than asynchronous operations. And alternative would be to use synchronous URL requests but run them from separate threads. The best way to achieve this is to use GCD (grand central dispatch).
Note that you may not update the user interface from background threads. Instead, when the URL request has finished and you want to display your results, you have to call performSelectorOnMainThread (part of NSObject) for that.