NSURLConnection 5 seconds delay - objective-c

i have the following code to make requests to my api:
-(void)makeApiCall:(NSString *)function params:(NSDictionary *)params notificationName:(NSString *)notificationName
{
NSURL *url = [NSURL URLWithString:kBaseUrl];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[NSString stringWithFormat:#"%#/%#",kApiBaseUrl,function] parameters:params];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(#"complete");
//..My code
}];
}
My problem is that, after the NSLog(#"complete"), the app is frozen for 5 seconds..
How can i fix that?
Thanks.

The queue parameter of sendAsynchronousRequest is the queue on which the
completion handler block is dispatched. If you do any UI updates in the completion
handler then you probably should call the method with
queue:[NSOperationQueue mainQueue]

Related

NSURL Request on Watch OS 2

I just updated Xcode to the latest watch beta and a project that I was working on before now has errors which hadn't been there.
Here is my code:
NSString *urlToSet = [[NSString alloc] initWithFormat:#"http://www.example.com/"];
self.responseData = [NSMutableData data];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:urlToSet]];
(void)[[NSURLConnection alloc] initWithRequest:request delegate:self];
The error is on the last line and it says:
'initWithRequest:delegate:' is unavailable: not available on watchOS
Since NSURLConnection is deprecated in iOS9, Apple is forcing everyone to switch to NSURLSession and Watch developers are not an exception. So just use the following code instead of yours:
NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString:urlToSet]];
[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];

Can I get a server's server time by querying its HTTP header?

Say, can I get http://www.google.com 's server time?
How to do that in ObjC?
Sure:
NSURLRequest *rq = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[NSURLConnection sendAsynchronousRequest:rq queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *resp, NSData *data, NSError *err) {
NSDictionary *headers = [(NSHTTPURLResponse *)resp allHeaderFields];
NSString *dateString = [headers objectForKey:#"Date"];
NSLog(#"Google Server Date: %#", dateString);
}];

breaking up an API connection call into functions Objective-C

This most likely a very trivial questions but I have a connection set up to an API in order to retrieve information. Right now I have everything setup in viewDidLoad. I know there is a more efficient way to place this information for later access by the user but I am too inexperienced in Objective-C to know how to do it. Here is how I have it all laid out at the moment.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.myurl.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSData *myResponse = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];
NSString *myString = [[NSString alloc] initWithData:myResponse encoding:NSUTF8StringEncoding];
SBJsonParser *myParser = [[SBJsonParser alloc] init];
NSArray *myData = [parser objectWithString:myString error:nil];
Try this:
- (void)loadAndParseURL:(NSString *)URLString
completion:(void (^)(NSArray *data))completion {
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:URLString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
dispatch_async(dispatch_get_global_queue(DISPATH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *myResponse = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];
NSString *myString = [[NSString alloc] initWithData:myResponse encoding:NSUTF8StringEncoding];
SBJsonParser *myParser = [[SBJsonParser alloc] init];
NSArray *myData = [parser objectWithString:myString error:nil];
completion(myData);
});
}
completion is a block (anonymous function pointer), which accepts an array as parameter and executes customized code blocks. The block holds the value of local variables, aka the 'environment', and evaluates its content at the time of execution(, usually with delay of some kind), in this example, after myData is parsed from myString. dispatch_async puts the anonymous block that sends the request and does other stuff onto a background queue (in a background thread, of course). So the calling to this method is returned before the response is even received, and you should arrange anything after within the completion block.
If spinning it off into a thread is what you are looking for I would use sendAsynchronousRequest
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
SBJsonParser *myParser = [[SBJsonParser alloc] init];
NSArray *myData = [parser objectWithString:myString error:nil];
// Do what you want with the response and subscribe
//anywhere in your code to get notified when it completed.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"NotifyMeWhenDone"
object:self];
}];

Delay when updating view in the completionHandler of an async HTTP request

In my app when the user presses a button I start a HTTP asynchronous request (using [NSURLConnection sendAsynchronousRequest...]) and change the text of UILabel in the completionHandler block. This change, however, does not take place when the request is concluded and instead happens around 2-3 seconds later. Below is a code snippet that results in this behavior.
- (IBAction)requestStuff:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://stackoverflow.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
exampleLabel.text = [NSString stringWithFormat:#"%d", httpResponse.statusCode];
}];
}
A similar behavior happens when I attempt to create an UIAlertView inside the completionHandler.
- (IBAction)requestStuff:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://stackoverflow.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([httpResponse statusCode] == 200) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"It worked!"
message:nil
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}];
}
A small difference, though, is that the screen dims when [alert show] is executed. The alert itself only appears 2-3 seconds later like in the previous scenario.
I'm guessing this is related to how the UI is handled by the app's threads, but I'm not sure. Any guidance on why the delay happens will be greatly appreciated.
According to The Apple Docs.
Threads and Your User Interface
If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.
Calling your UI updates on the main thread would solve this problem. Surround your UI code with a call to the main thread (below).
dispatch_async(dispatch_get_main_queue(), ^{
exampleLabel.text = [NSString stringWithFormat:#"%d", httpResponse.statusCode];
});
There are other ways to do calls on the main thread, but using the simpler GCD commands would do the job. Again, see the Threaded Programming Guide for more info.
This could happen because all UI stuff should be called in a main queue. Try this:
- (IBAction)requestStuff:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://stackoverflow.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
dispatch_async(dispatch_get_main_queue(), ^{
exampleLabel.text = [NSString stringWithFormat:#"%d", httpResponse.statusCode];
});
}];
}
You can try to create a method that sets the text, and inside the block you call:
[self performSelectorOnMainThread:#selector(mySelector) withObject:nil waitUntilDone:NO];
The selector will call and executed on main thread. Hope this help....

Waiting for completion block to complete in an AFNetworking request

I am making a JSON request with AFNetworking and then call [operation waitUntilFinished] to wait on the operation and the success or failure blocks. But, it seems to fall right though - in terms of the log messages, I get "0", "3", "1" instead of "0", "1", "3"
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://google.com"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSLog(#"0");
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, id JSON) {
NSLog(#"1");
gotResponse = YES;
} failure:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"2");
gotResponse = YES;
}];
NSLog(#"Starting request");
[operation start];
[operation waitUntilFinished];
NSLog(#"3");
This works by using AFNetworking to set up the requests, but making a synchronous call then handling the completion blocks manually. Very simple. AFNetworking doesn't seem to support this https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ, though the work around is simple enough.
#import "SimpleClient.h"
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
#implementation SimpleClient
+ (void) makeRequestTo:(NSString *) urlStr
parameters:(NSDictionary *) params
successCallback:(void (^)(id jsonResponse)) successCallback
errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error) {
errorCallback(error, nil);
} else {
id JSON = AFJSONDecode(data, &error);
successCallback(JSON);
}
}
#end
That should (almost) work. Your call to
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
should probably not pass [url path] to the path: parameter. In AFNetworking land, that path is everything after the base url (for example the base url could be "http://google.com" and the path "/gmail" or whatever).
That being said, it's probably not a good idea to make the asynchronous operation into a thread-blocking synchronous operation with waitUntilFinished, but I'm sure you have your reasons... ;)
I just had the same problem and found a different solution. I had two operations that depend on each other, but can load in parallel. However, the completion block of the second operation can not be executed before the completion block of the first one has finished.
As Colin pointed out, it might be a bad choice to make a web request block. This was essential to me, so I did it asynchronously.
This is my solution:
// This is our lock
#interface SomeController () {
NSLock *_dataLock;
}
#end
#implementation
// This is just an example, you might as well trigger both operations in separate
// places if you get the locking right
// This might be called e.g. in awakeFromNib
- (void)someStartpoint {
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url1]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// We're done, we unlock so the next operation can continue its
// completion block
[_dataLock unlock];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// The request has failed, so we need to unlock for the next try
[_dataLock unlock];
}];
AFJSONRequestOperation *operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url2]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// The completion block (or at least the blocking part must be run in a
// separate thread
[NSThread detachNewThreadSelector:#selector(completionBlockOfOperation2:) toTarget:self withObject:data];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// This second operation may fail without affecting the lock
}];
// We need to lock before both operations are started
[_dataLock lock];
// Order does not really matter here
[operation2 start];
[operation1 start];
}
- (void)completionBlockOfOperation2:(id)data {
// We wait for the first operation to finish its completion block
[_dataLock lock];
// It's done, so we can continue
// We need to unlock afterwards, so a next call to one of the operations
// wouldn't deadlock
[_dataLock unlock];
}
#end
Use Delegate method call
Put the method inside block which will call itself when downloading/uploading completes.