So I have been trying to create an app for a website and I've got the "Log in" page working except when it won't transition to the next view.
This is the code that I believe is causing the problem :
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSString *str=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(#"%#", str);
if ([str rangeOfString:#"The username or password you provided is invalid. Please try again."].location == NSNotFound) {
loginPageStatusLabel.text = #"Correct";
NSLog(#"Correct Login");
[self performSegueWithIdentifier:#"toHome" sender:self];
} else {
loginPageStatusLabel.text = #"Incorrect";
NSLog(#"Login Failed");
}
}];
* Assertion failure in -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished], /SourceCache/UIKit_Sim/UIKit-2935.137/Keyboard/UIKeyboardTaskQueue.m:368
2014-05-11 00:06:51.426 LoginTests[3381:3e03] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIKeyboardTaskQueue waitUntilAllTasksAreFinished]' may only be called from the main thread.'waitUntilAllTasksAreFinished]' may only be called from the main thread.
That is the error being thrown whenever I try to "Log in". The Segue with work if I run it alone, so I am assuming the problem is that the app is trying to go to the next View before it's ready and its causing an error.
I'm fairly new to Obj-C so if I have not posted the adequate information or not called things by the proper names please inform me.
Thank You!
I don't know what value you supplied for queue parameter, but given that your completion block is performing UI updates that must happen on the main thread, you can use [NSOperationQueue mainQueue] (or manually dispatch this code to the main queue). This queue parameter specifies what queue the completion block should be added to, and because you're doing UI related stuff in your completion block, this must be done on the main thread.
Having corrected that, if you are still have assertion errors, you can add an exception breakpoint and that will help confirm precisely where this assertion error is taking place. Or look at your stack trace.
I'd also, in addition to using [NSOperationQueue mainQueue], would suggest doing some more robust error handling:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (!data) {
// for example, no internet connection or your web server is down
NSLog(#"request failed: %#", error);
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
int statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
// for example, 404 would mean that your web site said it couldn't find the URL
// anything besides 200 means that there was some fundamental web server error
NSLog(#"request resulted in statusCode of %d", statusCode);
return;
}
}
// if we got here, we know the request was sent and processed by the web server, so now
// let's see if the login was successful.
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// I'm looking for "Welcome" ... I doubt that's right (but I don't have access to
// your web server, so I'm guessing). But the idea is that you have to find whatever
// appears after successful login that is not in the response if login failed
if ([responseString rangeOfString:#"Welcome"].location != NSNotFound) {
loginPageStatusLabel.text = #"Correct";
NSLog(#"Correct Login");
[self performSegueWithIdentifier:#"toHome" sender:self];
} else {
loginPageStatusLabel.text = #"Incorrect";
NSLog(#"Login Failed");
}
}];
Related
-(void)application:(UIApplication )application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSLog(#"In performFetchWithCompletionHandler");
DownloadAlerts alertDownload = [[DownloadAlerts alloc]init];
[alertDownload getAlertsOnLocUpdate];
completionHandler(UIBackgroundFetchResultNewData); // We will add content here soon.
}
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 && error == nil) {
NSLog(#"Hello01");
} else if ([data length] == 0 && error == nil) {
NSLog(#"Hello02");
} else if (error != nil) {
NSLog(#"Hello03");
}
}];
I am executing the above piece of code in a function that gets called via the performFetchWithCompletionHandler in appdelegate.h.
When I launch the app via background fetch, none of the 3 if blocks get executed. How do I implement this? I need to download data via background fetch. Kindly help... NSUrlSession also doens't work..
Have you tried to send synchronous request? I think your`s performFetchWithCompletionHandler method finishes before NSURLConnection receives the response. by the way, are you sure that performFetchWithCompletionHandler is called? Maybe you need to set capability for it?
Need to call "completionHandler(UIBackgroundFetchResultNewData);" after you received the data, pass it into "getAlertsOnLocUpdate" method as a parameter, and call it after you receive the answer from server (in completionHandler of URLConnection)
I'm dealing the authenticate issue with Tumblr account using [NSURLConnection sendAsynchronousRequest:queue:completionHandler:] to send the authenticate request, but here I meet a tough problem:
Whenever I send the request at the first time, everything goes perfectly, but when the first authentication is done and then resend the request second time, there comes "NSURLErrorDomain error -1012".
The authenticate page is loaded in a webview so that the authentication should be done in my app without a browser. But it is interesting that if the process runs in a browser there comes no error, errors only happen when using webview.
It was weird that the authentication goes with the same code, but only the first authentication can be done, only if I reinstall the app can I authenticate it again, and after this the problem comes again.
I did everything I can chase to solve the issue, I clean the cache and cookie in webview, step the authentication process to see parameters, set the cachePolicy of the request but nothing helps.
I also found that on ios6 the process goes without any error. But on ios7 I get the -1012.
code -1012 tells me that the user cancelled the authentication, but the process goes automatically and I do not cancel it.
I'm wondering if the problem comes from the NSURLConnection.
- (void)authenticate:(NSString *)URLScheme WithViewController:(UIViewController *)con callback:(TMAuthenticationCallback)callback {
self.threeLeggedOAuthTokenSecret = nil;
self.hostViewController = con;
self.callback = callback;
[self emptyCookieJar];
NSString *tokenRequestURLString = [NSString stringWithFormat:#"http://www.tumblr.com/oauth/request_token?oauth_callback=%#", TMURLEncode([NSString stringWithFormat:#"%#://tumblr-authorize", URLScheme])];
NSLog(#"%#", tokenRequestURLString);
NSMutableURLRequest *request = mutableRequestWithURLString(tokenRequestURLString);
NSLog(#"%#", request);
[[self class] signRequest:request withParameters:nil consumerKey:self.OAuthConsumerKey
consumerSecret:self.OAuthConsumerSecret token:nil tokenSecret:nil];
[self openOAuthViewController];
NSURLConnectionCompletionHandler handler = ^(NSURLResponse *response, NSData *data, NSError *error) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (error) {
if (callback) {
callback(nil, nil, error);
}
return;
}
NSLog(#"%d", statusCode);
if (statusCode == 200) {
self.threeLeggedOAuthCallback = callback;
NSDictionary *responseParameters = formEncodedDataToDictionary(data);
self.threeLeggedOAuthTokenSecret = responseParameters[#"oauth_token_secret"];
NSURL *authURL = [NSURL URLWithString:
[NSString stringWithFormat:#"http://www.tumblr.com/oauth/authorize?oauth_token=%#",
responseParameters[#"oauth_token"]]];
[self initOAuthViewControllerWithURL:authURL];
} else {
if (callback) {
callback(nil, nil, errorWithStatusCode(statusCode));
}
}
};
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:handler];
}
Code above, everything goes normally before [NSURLConnection sendAsynchronousRequest:queue:completionHandler:],and after this method I got the error in completionHandler.
i want to get all articles from the shopware api(http://wiki.shopware.de/Shopware-API_cat_919.html)
but the i dont get the data into an NSDictionary
url i call: http://myshop.com/api/articles
here is the source i got
NSURL *url = [NSURL URLWithString:weburl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil) {
NSDictionary *rest_data = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
_newsDataForTable = [NSMutableArray array];
NSDictionary *news;
for (id key in rest_data[#"postalcodes"]) {
news = [rest_data[#"postalcodes"] objectForKey:key];
}
int iterator = 0;
for (id key in news) {
[_newsDataForTable insertObject:key[#"title"] atIndex:iterator];
iterator++;
}
[_newsTable reloadData];
[_newsTable numberOfRowsInSection:[_newsDataForTable count]];
[_newsTable reloadRowsAtIndexPaths:0 withRowAnimation:UITableViewRowAnimationLeft];
}
}];
}
There are a couple of things in your approach that could use improvement.
First, this is performing networking on the main queue. That is a no-no, wether the networking is synchronous or not. Creating a new NSOperationQueue for your connections and passing that instead of [NSOperationQueue mainQueue] is a huge improvement.
Second, the error handling is incorrect. In general the correct error handling pattern for Objective-C is to check wether a call resulted in the expected result before using the error. In this case, it's the NSURLResponse that should be checked, not the data. NSURLConnection may be able to connect to the remove service just fine but get no data back - and for many HTTP requests this is expected, correct behavior. If there is a problem connecting, the NSURLResponse will be nil. Check wether the response is nil, if it is then handle the error.
You're also not checking the HTTP response status code or MIME type. The server could respond with a 500, indicating a server error, or could mistakenly send you HTML (which would give the JSON parser fits).
A verbose example that does the above correctly is here. :
[NSURLConnection sendAsynchronousRequest:request queue:[self connectionQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (response != nil){
if ([[self acceptableStatusCodes] containsIndex:[(NSHTTPURLResponse *)response statusCode] ]){
// The server responded with an HTTP status code that indicates success
if ([[self acceptableMIMETypes] containsObject:[[response MIMEType] lowerCaseString] ]){
// The server responded with a MIME type we can understand.
if ([data length] > 0){
NSError *jsonError = nil;
id jsonObject = nil;
// The server provided data in the response, which means we can attempt to parse it
// Note that we are not specifying NSJSONReadingMutableContainers or NSJSONReadingMutableLeaves, as this would result in
// an object that is not safe to use across threads.
jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
if (jsonObject != nil){
// The JSON parser successfully parsed the data, and returned an object. There is nothing to tell us what kind of object was returned.
// We need to make sure it responds to the selectors we will be using - ideally, we'd pass this object to a method that takes an
// id parameter, not NSDictionary, and inside that method it would check wether the id object responds to the specific selectors
// it is going to use on it.
if ([jsonObject respondsToSelector:#selector(dictionaryWithDictionary:)]){
[self doStuffWithDictionary:jsonObject];
}
} else {
// The JSON parser was unable to understand the data we provided, and the error should indicate why.
[self presentError:jsonError];
}
} else {
// The server responded with data that was zero length. How you deal with this is up to your application's needs.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// The server response was a MIME type we could not understand. How you handle this is up to you.
}
} else {
// The server response indicates something went wrong: a 401 Not Found, etc.
// It's up to your application to decide what to do about HTTP statuses that indicate failure.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// Only inspect the error parameter if the response is nil.
// The error indicates why the URL loading system could not connect to the server.
// It is only valid to use this error if the server could not connect - which is indicated by a nil response
[self presentError:connectionError];
}
}];
// Returns the HTTP status codes we find acceptable.
- (NSIndexSet *) acceptableStatusCodes {
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 99)];
}
// Returns the mime types we can accept and understand.
- (NSSet *) acceptableMimeTypes {
NSSet *result = nil;
result = [NSSet setWithObjects:#"application/json", #"application/json; charset=utf-8", nil];
return result;
}
// Generic error handling method.
- (void) presentError:(NSError *)error {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
}
Yup, that's a lot of code, and it should be broken into smaller methods - but it illustrates the logic that should be implemented.
The NSError you are getting now
In your comments you indicate that you are getting an NSError with the domain NSURLErrorDomain and code -1002. If you look at NSURLErrors.h, you will see that NSURL errors map to CFURL errors. If you look at CFNetworkErrors.h, you can see that error code -1002 is kCFURLErrorUnsupportedURL. The URL loading system thinks the URL you are using is not a supported type. This is most likely because the scheme of your URL is incorrect, or how you are attempting to pass credentials as part of the URL is incorrect. Elsewhere in your comments you indicate you are passing credentials as follows:
username:apikey:someurl.com/foo/
Which should be more like:
https://username:apikey#someurl.com/foo/
But only if the service you are accessing is using a supported HTTP authentication type (i.e. Basic authentication). Either way, correctly composing the URL will fix the error you are currently seeing.
I want to call a unspecified number of URL requests which must be fired one after other. As the server can´t handle multiple requests with identical user-ID at the same time (only the last request is processed) i have to send my requests in an interval with about 1 seconds of gap. I did that within a dispatch_after block and increasing delays. But this is neither really secure nor elegant.
I´ve been just reading all day about GCD and want to try to change my code to send URL requests in a chain. My server connection class is build upon a NSURLConnection with asynchronuous request. That means it wouldn´t work with dispatch_async as the method call returns immediately back and the next request in the dispatch queue is called (which is probably immediately). But i have to wait for the response of the server until i may send the next request. My server connection class sends back via a delegate, but with dispatch_async it is never sending any deletate callbacks. Anyhow it wouldn´t work this way.
Probably it is better to put all requests into a NSArray and then call a method which will send requests from the array to the connection class and the delegate callback will pop the item from the array and sending the next request till all requests are done. Unfortunately i absolutely have no idea how i could store the requests and parameters in an array. Currently my call looks like that:
- (void)sendSettings
{
//NSLog(#"begins: %s", __FUNCTION__);
dataProtocol = [[BackgroundSoundConnection alloc] init];
[dataProtocol setDelegate:self];
//double delayInSeconds;
//dispatch_time_t popTime;
//delayInSeconds = 0.1f;
if (self.switch1.on)
{
if (![self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundNextCall/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"nextCall" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundNextcall"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch2.on)
{
if (![self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundIncoming/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"incomingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundIncoming"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch3.on)
{
if (![self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundOutgoing/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"outgoingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundOutgoing"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
for (int i = 0; i < [personArray count]; i++)
{
if (![personArray[i] connectedToServer])
{
NSLog(#"sound: %#", [personArray[i] soundId]);
NSLog(#"msisdn: %#", [personArray[i] personMSISDN]);
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundContext/%#/%#", [personArray[i] soundId], [personArray[i] personMSISDN]] httpMethod:#"PUT" sound:self.sound stickerType:#"contextCalls" personMSISDN:[personArray[i] personMSISDN]];
}
}
[self animateViewAway:self.view];
}
A part of the request parameters is already in an array. I could use this array and push the other request parameters into it and then sending the first parameter. And after server responded send the next request triggered by the callback from the delegate. Probably this would work.
But i´m just wondering if there isn´t andy way to que the requests a dispatch queue. But how could i que the delegates as well? Or what do i have to do that the queue will wait until the server responds? I´d like to avoid rewriting my server connection class from asynchronous to synchronous URLConnection which would probably make the difference.
Can anybody point me to a solution with asynchronous URLConnection and dispatch_async?
I haven´t seen the possibilites of NSOperation and NSOperationQueue yet. In the podcast of Jeff Kelley i´ve heard that the advantage of GCD over NSOperation is the dependencies feature. http://iphreaksshow.com/042-iphreaks-show-concurrency-with-jeff-kelley/
Or did i mix up everything? What would you recommend?
A complete NSURLRequest represents a complete request by containing a path, query params or body, headers, etc. You can build several of these to represent your several server requests.
NSURLConnection provides an asynch send (sendAsynchronousRequest:queue:completionHandler:). A naive way to sequence a series of requests, is to nest the requests in completion blocks as follows...
[NSURLConnection sendAsynchronousRequest:request0 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
[NSURLConnection sendAsynchronousRequest:request1 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// and so on... yikes, we'll have code in column 1000 pretty soon
But it should be clear that this is a weak idea. You can get the same effect for sequencing an arbitrary number of requests with pretty compact code as follows:
- (void)doManyRequests:(NSArray *)requests withResults:(NSMutableArray *)results completion:(void (^)(void))completion {
if (!requests.count) {
return completion();
}
NSURLRequest *nextRequest = requests[0];
NSArray *remainingRequests = [requests subarrayWithRange:NSMakeRange(1, requests.count-1)];
[NSURLConnection sendAsynchronousRequest:nextRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[results addObject:data];
[self doManyRequests:remainingRequests withResults:results completion:completion];
}];
}
Now, as you suggested, prepare several requests and place them in an array:
NSURLRequest *request0 = // however you build this for a given user id
NSURLRequest *request1 = // etc.
NSURLRequest *request2 = // etc.
NSArray *requests = #[request0, request1, request2];
NSMutableArray *results = [NSMutableArray array];
[self doManyRequests:requests withResults:results completion:^{
NSLog(#"this will be an array of NSData objects %#", results);
}];
I am downloading a bunch of largish zip files with the following method. It can take a little while and so I'd like to display a progress bar.
I've researched how to do with with the delegate methods for NSURLConnection and it seems straightforward, however I want to achieve the same thing with "sendAsynchronousRequest". How can I get the number of bytes downloaded as it downloads as well as the total number of bytes expected so that I can display a progress bar? I understand that I cannot use the delegate methods if I kick off a download in the manner I am doing it.
// Begin the download process
- (void)beginDownload:(NSMutableArray *)requests {
// Now fire off a bunch of requests asynchrounously to download
self.outstandingRequests = [requests count];
for (NSURLRequest *request in requests) { // Get the request
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Error check
if ( error != nil ) {
// The alertview for login failed
self.appDelegate.warningView.title = #"Refresh Error!";
self.appDelegate.warningView.message = [error localizedDescription];
// Show the view
[self.appDelegate.warningView show];
// Debug
if ( DEBUG ) {
NSLog(#"A request failed - %d left!",self.outstandingRequests);
}
}
else {
// Debug
if ( DEBUG ) {
NSLog(#"A request is done - %d left!",self.outstandingRequests);
}
}
// Decrement outstanding requests
self.outstandingRequests--;
// No requests are left
if (self.outstandingRequests == 0) {
// Debug
if ( DEBUG ) {
NSLog(#"All requests are done!");
}
// Get rid of loading view
[self performSelector:#selector(dismissLoadingView) withObject:nil afterDelay:0.15];
}
}];
}
}
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLConnectionDownloadDelegate_Protocol/NSURLConnectionDownloadDelegate/NSURLConnectionDownloadDelegate.html#//apple_ref/doc/uid/TP40010954-CH2-SW1
How to make an progress bar for an NSURLConnection when downloading a file?
http://iphonedevsdk.com/forum/iphone-sdk-development/24233-nsurlconnection-with-uiprogressbar.html
http://iphoneeasydevelopment.blogspot.com/2011/10/use-progess-bar-when-downloading-file.html
sendAsynchronousRequest won't work for your purposes as it doesn't call your callback until the request has completed. You'll need to use initRequest:withDelegate: and handle your own data accumulation.
When the header is received (possibly multiple times for redirects) your didReceiveResponse method will be called, you can pick up the expected size there:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
_expectedBytes = (NSUInteger)response.expectedContentLength;
_data = [NSMutableData dataWithCapacity:_expectedBytes];
// make a progress update here
}
You'll receive a call to the delegate method didReceiveData each time a chunk of data is received, so you know how much data you've received up to this point.
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_data appendData:data];
_receivedBytes = _data.length;
// make a progress update here
}