NSURLConnectionDelegate callback in one function - ios

I'm trying to make my own Request class I intend to use throughout my app. Here is the code I've been coming up with so far.
-(IIWRequest *)initAndLaunchWithDictionnary:(NSDictionary *)dictionnary
{
self=[super init];
if (self) {
// Create the request.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://xxxxx.com/app/"]];
// Convert data
SBJsonWriter *jsonWriter = [[SBJsonWriter alloc] init];
NSString *jsonData = [jsonWriter stringWithObject:dictionnary];
NSLog(#"jsonData : %#",jsonData);
NSData *requestData = [jsonData dataUsingEncoding: NSUTF8StringEncoding];
request.HTTPBody = requestData;
// This is how we set header fields
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody: requestData];
// Create url connection and fire request
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[self activateNetworkActivityIndicator];
if (connection) {
NSLog(#"Connection");
} else {
NSLog(#"No connection");
}
}
return self;
}
I have included NSURLConnectionDelegate. I'd like to fire the connection callbacks such as did finished or did fail back to the function mentioned before. The goal of all that is to get only one method to call in the end looking like :
-(IIWRequest *)initAndLaunchWithDictionnary:(NSDictionary *)dictionary inBackgroundWithBlock:^(BOOL succeeded){}
Any idea ? Thanks !

Use block method of NSURLConnection class it will reduced your functionality as well sendAsynchronousRequest:queue:completionHandler:
Read this doc.

I would hardly suggest you to use one of the currently existing libraries for calling URLs. One of the best I know is AFNetworking https://github.com/AFNetworking/AFNetworking. There is lot of examples and its easy to use and I am sure you should go with it.
Anyway, if you want to build your own class I would suggest you to read post written by Kazuki Sakamoto here NSURLConnection and grand central dispatch.
Regards

If you are using the iOS 7, I recommend A LOT you to use NSURLSession classes, this new network api is really amazing and simple.
Anyway, to answer your question, you just need to hold the reference of callback in your class and call it when you receive some response from the server.
To hold the reference, you can do something like this:
// in your .h file
typedef void (^ResponseBlock)(BOOL success);
// in your .m, create a class extension and put declare the block to use it for callback
#interface MyClass ()
{
ResponseBlock callback;
}
// You can store reference using equal like this
- (void)myMethodRequestWithResponseBlock:(ResponseBlock)responseBlock
{
callback = responseBlock;
// statements
}
// And finally, you call back block simple like this:
callback(success);
Again, use NSURLSession api if you can, you will simplify your work.
I hope this may help you.
Cheers!

Related

Using NSURLSession to POST, what is the correct way to post the variables?

I am following this tutorial: http://www.raywenderlich.com/2965/how-to-write-an-ios-app-that-uses-a-web-service. Trying to set up a basic web service. Seems like the tutorial is old material and ASIHTTPRequest is no longer continued. I have been trying to use NSURLRequest instead. First question, is NSURLRequest a pretty standard way to be doing this? I just want something for basic GET, POST etc, should I be doing it a different way?
My code is:
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"We want to unlock for the code %#",self.textField.text);
//Get a device ID, (actually can't do this aymore)
NSString *uniqueIdentifier = #"My iPhone";
NSString *code = self.textField.text;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.madasd.co/promos/"]];
request.HTTPMethod=#"POST";
//Set the header fields
[request setValue:#"application/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString *myString = [NSString stringWithFormat:#"rw_app_id=1&code=%#&device_id=%#",code,uniqueIdentifier];
NSLog(#"%#",myString);
NSData *requestBodyData = [myString dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody=requestBodyData;
//Create url and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[conn start];
return TRUE;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",string);
}
Second question, I have tested the backend using curl so I know it works fine, however the response I get is "Invalid Request", I think this is because the string I am sending is not correct. Am I doing this correct using the var names and & operators? Any pointers on this would be great! thanks. (Running a LAMP server on Linode!)
EDIT:
Also tried sending as JSON:
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSDictionary *mapData = [[NSDictionary alloc]initWithObjectsAndKeys:#"1",#"rw_app_id",code,#"code",uniqueIdentifier,#"device_id", nil];
NSError *error = nil;
NSData *requestBodyData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
request.HTTPBody=requestBodyData;
Still getting the same error.
A couple of thoughts:
Don't use NSURLConnection. It is deprecated as of iOS 9. Use NSURLSession. See Using NSURLSession in the URL Loading System Programming Guide.
Decide what type of request you need to prepare. You specified application/xml in your header, but are creating a application/x-www-form-urlencoded request. Your Content-Type header must match how you're building the HTTPBody.
What type of request does your server require? x-www-form-urlencoded? XML? JSON?
Also, what type of response does your server provide?
If building a application/x-www-form-urlencoded request (as suggested by the body of your request), you are not properly percent escaping the values (see https://stackoverflow.com/a/20398755/1271826).
If you use delegate based NSURLConnection or NSURLSession, you should not just grab the results in didReceiveData. What you need to do is
Instantiate a NSMutableData before starting the request;
Have didReceiveData merely append to that NSMutableData;
Only when connectionDidFinishLoading: (in NSURLConnection) or URLSession:task:didCompleteWithError: (in NSURLSession) is called, should you then use the NSMutableData.
Alternatively, if using the block-based NSURLSession, this concern is completely eliminated (since you're not implementing any delegate methods). Using completionHandler-based methods of NSURLSession is much easier.
If all of this is too complicated, you might consider using AFNetworking's AFHTTPSessionManager (but not AFHTTPRequestOperationManager) to build your requests. It gets you out of the weeds of properly building requests, implementing delegate methods, etc.
You might need to wrap the strings into a dictionary and get the NSData object from a call to NSJSONSerialization. Though it depends on the form expected by the server.

Multiple NSURLConnection in a row

Basically I got 2 NSURLConnections.
The first gets a token from my API and the second gets some events from the API but needs the token to do that. I'm currently learning objective C/Ios stuff so this is not a real app.
What I'm trying to do is to distinguish between the two connections by my class property current_connection.
1) My first question is: Is there a better way to do this?
2) Second question: Is it safe to make 2 connections like this? Because these have to happen in this order, I don't know if IOS will always do that if my code is like this. For example if the first requests takes very long does it already start the second? That would be a problem without having a token.
3) I read about https://github.com/AFNetworking/AFNetworking - should I use it?
My Code:
HomeViewController.h
#import <UIKit/UIKit.h>
#interface HomeViewController : UIViewController
#property (nonatomic, strong) NSString* current_connection;
#end
My HomeViewController.m
#import "HomeViewController.h"
#interface HomeViewController ()
#end
#implementation HomeViewController
#synthesize current_connection;
- (void)viewDidLoad {
[super viewDidLoad];
//get user token
NSString *post = [NSString stringWithFormat:#"&email=%#&password=%#",[[NSUserDefaults standardUserDefaults] objectForKey:#"email"], [[NSUserDefaults standardUserDefaults] objectForKey:#"password"]];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%lu",(unsigned long)[postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://localhost:3000/api/v1/sessions"]]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Current-Type"];
[request setHTTPBody:postData];
current_connection = #"token";
NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self];
//load events
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSString *url =[NSString stringWithFormat:#"http://localhost:3000/api/v1/events/start_events?user_email=%#&user_token=%#",[[NSUserDefaults standardUserDefaults] objectForKey:#"email"], [[NSUserDefaults standardUserDefaults] objectForKey:#"token"]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[req setHTTPMethod:#"GET"];
current_connection = #"event";
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data {
NSError *error;
NSMutableDictionary *cdata = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves error:&error];
if( error )
{
NSLog(#"%#", [error localizedDescription]);
}
else {
if ([current_connection isEqual: #"token"]) {
NSString *token = cdata[#"token"];
NSLog(#"%#", token);
//save token to user defaults
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:token forKey:#"token"];
[defaults synchronize];
}
else {
//display events
NSLog(#"%#", cdata[#"events"]);
}
}
}
#end
In response to your questions:
Yes, there are better ways to do this. There are a number of issues:
Your two connection requests will be calling the same delegate object, and they'll step over each other unless you are very careful.
In your case, since the second connection needs the first to finish (i.e. you need the token, presumably, before you start the next connection), you could fix this by putting the initiation of the second connection not immediately after the first was initiated, but where the first finishes (i.e. where you get the token).
Right now, your two requests are effectively running concurrently (you have no formal assurance as to which will finish first). You want to change this so that they run consecutively.
You are processing the responses in didReceiveData. That is dangerous because sometimes the body of a response will require more than one call to didReceiveData. Usually you (a) instantiate some NSMutableData object before initiating the request; (b) didReceiveData just appends the received data to that NSMutableData; and (c) only in connectionDidFinishLoading would you try to now use the whole NSMutableData, and process that for the web service's response.
Bottom line, this delegate based implementation has a number of problems, and I'd suggest using sendAsynchronousRequest (a nice block-based implementation that gets you out of these weeds) or AFNetworking (a nice wrapping of the delegate-based implementation in NSOperation objects). You could fix this delegate-based implementation, but it might be easier to use one of the aforementioned.
No, it is not prudent to use the same delegate object to issue two concurrent requests. Now, it turns out that you probably don't want them to be concurrent, anyway, but as a general design principle, life is easier if you have different delegate object for each NSURLConnection request.
Yes, AFNetworking is probably worth trying out. It's not perfect, but it gets you out of the weeds of the NSURLConnectionDataDelegate programming. If you don't need the full richness of AFNetworking, you could also issue requests as simple as these with NSURLConnection class method sendAsynchronousRequest.
its better to use + (void)sendAsynchronousRequest:(NSURLRequest*) request
queue:(NSOperationQueue*) queue
completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler NS_AVAILABLE(10_7, 5_0); instead of using delegates.
You can create as much connection as you want there is no problem.
AFNetworking is a wonderful library that will help you to do so many things like will check for network availability and downloading stuffs asynchronously. Depending on your usage you can decide. If your app have so many network activities and need to handle different error condition better to go for AFNetwork.

set flag before connection start

in viewDidLoad I want to call webservice for several times.
So my code in viewDidLoad is as follows
//Webservice call for industry list
NSURL *aUrl = [NSURL URLWithString:[NSString stringWithFormat:#"%#index.php/industry/industrylist",baseurl]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[#"emailid=b#b.com&password=1234" dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection= [[NSURLConnection alloc] initWithRequest:request delegate:self];
//set flag for industryList
flag = #"industry";
[connection start];
//Webservice call for function list
NSURL *bUrl = [NSURL URLWithString:[NSString stringWithFormat:#"%#index.php/functionmdl/allFunctionlist",baseurl]];
NSMutableURLRequest *requestb = [NSMutableURLRequest requestWithURL:bUrl
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[requestb setHTTPMethod:#"POST"];
[requestb setHTTPBody:[#"" dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connectionb = [[NSURLConnection alloc] initWithRequest:requestb delegate:self];
//set flag for industryList
flag = #"functionmdl";
[connectionb start];
But the value of flag is always set to functionmdl because i have assigned the flag with the string at the just previous line of the last line in the above code. I know that i am setting the flag in wrong way. So, please let me know how can i set flag here. Basically i want to use these flag in connectionDidFinishLoading method.
i have to differentiate the webservice response data according to webservice call.
Please help me to resolve this.
I think you can use the connection.currentRequest.URL to distinguish the request.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *strUrl = [connection.currentRequest.URL absoluteString] ;
// compare to the url of your request to distinguish them
}
There are several approaches here (none involve a flag).
The easiest is typically to use [NSURLConnection sendAsynchronousRequest:queue:completionHandler]. Then you can put your connectionDidFinishLoading code right here, specific to each request.
That doesn't work if you need more advanced features of NSURLConnectionDelegate (like responding to authentication requests). In that case, I usually recommend that you wrap up the connection and delegate into a separate object, and instantiate one for each connection. That way each object is only delegate for a single connection.
In a small number of cases, this still isn't appropriate, and in those cases you can check the connection's request in the delegate (connection.currentRequest) to determine which one you're being called about. In some cases, I've created a mutable dictionary property mapping NSURLRequest information to some other piece of metadata I wanted in the handlers.
And in the most fancy (and therefore least-often used) case, you can attach metadata (such as an identifier) to your connection using objc_setAssociatedObject, but this is seldom necessary.
But I'd look at [NSURLConnection sendAsynchronousRequest:queue:completionHandler]. It's the simplest to use and addresses the most common cases easily.

iOS: using async

I currently have a screen with 2 tables. I'm getting the data synchronously and putting it on the screen. Code looks something like:
viewController.m
DBAccess_Error_T = [getList:a byCompanyID:1];
DBAccess_Error_T = [getList:b byCompanyID:2];
[self putListAOnScreen];
[self putListBOnScreen];
DBAccess.m
+ (DBAccess_Error_T)getList:(NSMutableArray*)a byCompanyID:(NSInteger)cID
{
// Pack this up in JSON form
[self queryDB:postData];
// Unpack and put it into variable a
}
+ (id)queryDB:(id)post
{
// Send request
// Get back data
}
I'm now trying to switch this over to async and I'm struggling. It's been hard even with website tutorials and documentations.
Since all of my database utilities are in separate files from the viewControllers, I'm not sure how I can use the didReceiveData and didReceiveResponse handlers. Also, since I have 2 arrays to fill for my 2 tables, how do I distinguish the difference in didReceiveData?
Instead, what I'm trying to do now is use sendAsynchronousRequest, but it seems I need to create an unpack function for every send function...let me know if I'm way off here...it looks something like:
viewController.m stays the same
DBAccess.m
+ (DBAccess_Error_T)getList:(NSMutableArray*)a byCompanyID:(NSInteger)cID
{
NSDictionary *post = /*blah blah*/
[self queryDB:post output:(a)];
}
+ (id)queryDB:(id)post output:(id)output
{
NSError *error;
NSData *jsonPayload = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:jsonPayload];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil)
{
[self unpackDataForList:output data:data]; // This function needs to be different depending on which function called queryDB...the data will be unpacked in a different way
}
}
}
+ (void)unpackDataForList:(id)output data:(NSData*)data
{
// Do my unpacking here and stick it into 'output'.
}
How can I call a different unpackData function? are function pointers the right way to do this? Is this approach way off? Any tips would be greatly appreciated!
Have you ever looked at ASIHTTPRequest? It makes your life a lot easier by allowing you to use blocks. Here's an example of how to make an asynchronous request:
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
You can find more information here:
http://allseeing-i.com/ASIHTTPRequest/

When accessing the web, what is the different between block and without block

I am using ASIHTTPRequest framework, in the document, what are the differences between the 2nd and 3rd example, in usage, advantage and disadvantage?
2nd example (Creating an asynchronous request):
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
3rd example (Using blocks)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
The blocks in iOS are a part of Concurrent Programming
You use a block when you want to create units of work (that is, code segments) that can be passed around as though they are values. Blocks are usually used for writting a callbacks.
Usually, using blocks do not reflect in different applicatino behaviour. The syntactical difference is that, when using blocks you do not need to define a request delegate or implement delegate methods (such as -requestFinished: and -requestFailed:) for async requests.
One of the advantages is in accessing local method variables in completion block, bacause the function expression in block can reference and can preserve access to local variables (like variable url in your method -grabURLInBackground: or any other local variable defined in your method).
The second adventage is in using nested request calls. For example, you may need to perform a few requests in sequence, and without blocks you will need to define a separate delegate method callback for each request, which may result in reduced readability of your code.
Blocks allow you to write code at the point of invocation that is executed later in the context of the method implementation, which may be very usefull, when you get used to using them.
Some patterns to avoid when using blocks are mentioned in Apple Blocks Programming Topis

Resources