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.
Related
When my iOS app starts, I need to get some critical settings from my server (example: http://www.example.com/critical_app_settings.php). I need to get this data before I load up the user's data.
What is the proper way to make this call, basically "pause" the app, and yet still not block the main thread and be in good compliance?
Currently I am doing this example, which obviously isn't correct, because it completely blocks everything:
NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]];
[request setHTTPMethod: #"POST"];
[request setHTTPBody:myRequestData];
NSURLResponse *response;
NSError *error;
NSString *returnString = [[NSString alloc] init];
returnString = [[NSString alloc] initWithData:[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error]
encoding: NSASCIIStringEncoding];
Instead of using NSURLConnection sendSynchronousRequest you can use NSURLSessionDataTask. Also sendSynchronousRequest is deprecated from iOS 9. Code will be as follows
NSData *myRequestData = [NSData dataWithBytes:[myRequestString UTF8String] length:[myRequestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: myURLString]];
[request setHTTPMethod: #"POST"];
[request setHTTPBody:myRequestData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if ([httpResponse statusCode]==200) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* returnString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//do whatever operations necessary on main thread
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
//action on failure
});
}
}];
[dataTask resume];
Since it is async operation your main thread will not be blocked. As far as pausing app is concerned you can either show some kind of activity indicator or disable user interaction before making call. Once response comes hide indicator or enable the UI.
You should NEVER use sendSynchronousRequest. You should pretend that method does not exist. It's a very bad idea to call that on the main thread, and the system will likely kill your app if the remote server takes more than a few seconds to serve your request.
As Vishnu suggests, use NSURLSession instead.
If you want to "pause the app" while your content loads, display some sort of modal alert/view while the load takes place. One option is to use a UIAlertController with no actions added, and then call dismissViewController:animated: on it once the load is complete.
Another thing I've done is to display a view that completely covers the screen, filled in 50% opaque black, and put a view inside that with a progress indicator inside it, plus a please wait message (and any other cosmetic stuff you want.) The 50% opaque view both dims the rest of the screen and prevents the user from clicking on anything, and the progress indicator lets the user know that your app is working.
Both of these approaches work because your download is taking place asynchronously, so you can display an alert or a progress indicator and it animates as expected.
/**
You can use a protocol to handle that for you, for example you can create a class that connect to the URL you can use NSURLSession but it seems that you are familiar with NSURLConnection. So lets create your class that will connect and receive the information from your URL something like this:
The H file
*/
#import <Foundation/Foundation.h>
/* Here is the custome protocol to manage the response at will */
#protocol OnResponseDelegate;
/* Here the class is implementing the protocol to receive the callbaks from the NSURLConnection when the request is processing */
#interface WgetData : NSObject <NSURLConnectionDataDelegate>
#property (strong, nonatomic) id<OnResponseDelegate>handler;
#property (strong, nonatomic) NSMutableData *responseData;
#property (strong, nonatomic) NSURLConnection *connection;
#property (strong, nonatomic) NSMutableURLRequest *request;
-(id)initWithUrl:(NSString*)url postBody:(NSString*)body delegate:(id)delegate;
-(void)execute;
#end
/* here is the real definition of the protocol to manage the response */
#protocol OnResponseDelegate <NSObject>
-(void)onPreStart;
-(void)onResponse:(NSData*)response;
#end
/*
End H file
The code above is using a protocol to handle the data that you want to receive from the URL. lets do the implementation file it should look like this:
The M file
*/
#import "WgetData.h"
#define TIMEOUT 700
static NSString * const CONTENT_TYPE_VALUE = #"application/json";
static NSString * const CONTENT_TYPE_HEADER = #"Content-Type";
static NSString * const GET = #"GET";
static NSString * const POST = #"POST";
#implementation WgetData
#synthesize handler,responseData,connection,request;
-(id)initWithUrl:(NSString *)url postBody:(NSString *)body delegate:(id)delegate{
NSURL *requestUrl = [NSURL URLWithString:[url stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet.URLQueryAllowedCharacterSet]];
[self setRequest:[NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:TIMEOUT]];
[self setResponseData:[NSMutableData new]];
[self setHandler:delegate];
[request setHTTPMethod:POST];
[request addValue:CONTENT_TYPE_VALUE forHTTPHeaderField:CONTENT_TYPE_HEADER];
[request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];
return self;
}
-(void)execute{
//here is the moment to prepare something in your main class before send the request to your URL
[handler onPreStart];
[self setConnection:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]];
}
/* this method belongs to the implementation of the NSURLConnectionDataDelegate to receive the data little by little */
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.responseData appendData:data];
}
/* this method belongs to the implementation of the NSURLConnectyionDataDelegate that is runned only when the data received is complete */
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
/* if this method happen it means that the data is ready to delivery */
/* sending the information to the class that implements this class through the handler or delegate */
[handler onResponse:responseData];
}
#end
/*
End M file
Then you only need to implement this class with the next piece of code
For example in a ViewController inside a viewDidLoad
remember to call the protocol as a delegate to implement the methods in the right way
H file ViewController example
*/
#import <UIKit/UIKit.h>
#import "WgetData.h"
#interface ViewController : UIViewController <OnResponseDelegate>
#end
/*
End H ViewController file
M ViewController file
*/
#interface MainViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
WgetData *getData = [[WgetData alloc] initWithUrl:#"http://myurl.net" delegate:self];
[getData execute];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) onPreStart{
// prepare something before run the request to your URL
}
- (void) onResponse:(NSData *)response{
// receive the data when the data is ready and convert o parse to String or JSON or Bytes or whatever
}
#end
/* End M file
So with all this example you can manage the right moment when you will receive the data to manage at your own will, and your main thread app can wait while your data will be received to be ready for use.
*/
I am trying to create an xls sheet programmatically. To fill the sheet, I am making the multiple NSURLConnection around 100. Right now, my approach is :
Make a connection and store the data into an array . This array has 100 objects.
Now take the first object and call the connection . Store the data. And make the second connection with 2nd object in the array. This continues till the last object in the array.
It takes on average 14 seconds to finish the 100 connections. Is there any way to implement the NSURLConnection to get the response in a faster way?
Till yesterday I followed the basic approach like:
Declaring the properties:
#property (nonatomic,strong) NSURLConnection *getReportConnection;
#property (retain, nonatomic) NSMutableData *receivedData;
#property (nonatomic,strong) NSMutableArray *reportArray;
Initializing the array in viewDidLoad:
reportArray=[[NSMutableArray alloc]init];
Initializing the NSURLConnection in a button action :
/initialize url that is going to be fetched.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"****/%#/crash_reasons",ID]];
//initialize a request from url
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:tokenReceived forHTTPHeaderField:#"**Token"];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
//initialize a connection from request
self.getReportConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Processing the received data:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data{
if (connection==_getVersionConnection) {
[self.receivedData_ver appendData:data];
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *e = nil;
NSData *jsonData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData options: NSJSONReadingMutableContainers error: &e];
[JSON[#"app_versions"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (![obj[#"id"] isEqual:[NSNull null]] && ![reportArray_ver containsObject:obj[#"id"]]) {
[reportArray_ver addObject:obj[#"id"]];
}
NSLog(#"index = %lu, Object For title Key = %#", (unsigned long)idx, obj[#"id"]);
}];
if (JSON!=nil) {
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Version Reports succesfully retrieved" message:#"" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
}
}
Calling the another connection after one finishes:
// This method is used to process the data after connection has made successfully.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if (connection==getReportConnection) {
//check and call the connection again
}
}
And today, I tried the NSURLConnection with sendAsync to fire all the connections one after other using loop,and it worked pretty well.
self.receivedData_ver=[[NSMutableData alloc]init];
__block NSInteger outstandingRequests = [reqArray count];
for (NSString *URL in reqArray) {
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *connectionError) {
[self.receivedData appendData:data]; //What is the use of appending NSdata into Nsmutable data?
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *e = nil;
NSData *jsonData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData options: NSJSONReadingMutableContainers error: &e];
NSLog(#"login json is %#",JSON);
[JSON[#"app_versions"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (![obj[#"id"] isEqual:[NSNull null]] && ![reportArray_ver containsObject:obj[#"id"]]) {
[reportArray_ver addObject:obj[#"id"]];
}
NSLog(#"index = %lu, Object For title Key = %#", (unsigned long)idx, obj[#"id"]);
}];
outstandingRequests--;
if (outstandingRequests == 0) {
//all req are finished
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Version Reports succesfully retrieved" message:#"" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
}];
}
This time it took half the time to complete the 100 requests than the old procedure, Is there any faster way exists other than the asynReq?.What is the best scenario to use NSURLconnection and NSURLConnection with asyncReq?
A couple of observations:
Use NSURLSession rather than NSURLConnection (if you are supporting iOS versions of 7.0 and greater):
for (NSString *URL in URLArray) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
// configure the request here
// now issue the request
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// check error and/or handle response here
}];
[task resume];
}
If you absolutely have to issue 100 requests, then issue them concurrently like your sendAsynchronousRequest implementation (or my dataTaskWithRequest), not sequentially. That's what achieves the huge performance benefit.
Note, though, that you have no assurances that they'll completely in the order that you issued them, so you will want to use some structure that supports that (e.g. use NSMutableDictionary or pre-populate the NSMutableArray with placeholders so you can simply update the entry at a particular index rather than adding an item to the array).
Bottom line, be aware that they may not finish in the same order as requested, so make sure you handle that appropriately.
If you keep 100 separate requests, I'd suggest that you test this on a really slow network connection (e.g. use the Network Link Conditioner to simulate really bad network connection; see NSHipster discussion). There are problems (timeouts, UI hiccups, etc.) that only appear when doing this on slow connection.
Rather than decrementing a counter of number of pending requests, I'd suggest using dispatch groups or operation queue dependencies.
dispatch_group_t group = dispatch_group_create();
for (NSString *URL in URLArray) {
dispatch_group_enter(group);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
// configure the request here
// now issue the request
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// check error and/or handle response here
// when all done, leave group
dispatch_group_leave(group);
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do whatever you want when all of the requests are done
});
If possible, see if you can refactor the web service so you are issuing one request that returns all of the data. If you're looking for further performance improvement, that's probably the way to do it (and it avoids a lot of complexities involved when issuing 100 separate requests).
BTW, if you use delegate based connection, like you did in your original question, you should not be parsing data in didReceiveData. That should only be appending data to a NSMutableData. Do all of the parsing in connectionDidFinishLoading delegate method.
If you go to block-based implementation, this issue goes away, but just an observation on your code snippets.
Using sendAsynchronous is a great way to improve code organization. I'm sure with some careful scrutiny, we could improve the speed at the margin, but the way to noticeably improve speed is to not make 100 requests.
If the response bodies are small, create an endpoint that answers a conjunction of the results.
If the response bodies are large, then you're requesting more data than the user needs at the moment. Hold up the UI only on what user needs to see, and get the rest silently (... or, maybe better than silently, lazily).
If you don't control the server, and the response bodies are small, and the user needs all or most of to carry on with the app, then you can start working on performance at the margins and UI tricks to amuse user while the app works, but usually one of those constraints -- usually the latter -- can be relaxed.
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.
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!
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