I am trying to create an authentication system in an iOS app that allows a user to log in as well as register if they do not already have an account. I got the login system completely up and running yesterday, but when I got the code set up for the registration system, the code would not even ping the server. I then tried to test the login system again, and the code will not ping the server now either.
Relevant code for the RegistrationTableViewController (it’s a custom TVC that contains text fields in some of the cells – think of the view to create a new calendar event, for example):
- (IBAction)signUpButtonPressed {
// Get the values out of the text fields that the user has filled out.
NSString *email = self.emailTextField.text;
NSString *firstName = self.firstNameTextField.text;
NSString *lastName = self.lastNameTextField.text;
NSString *password = self.passwordTextField.text;
// Assuming that sign-up could potentially take a noticeable amount of time, run the
// process on a separate thread to avoid locking the UI.
dispatch_queue_t signUpQueue = dispatch_queue_create("sign-up authenticator", NULL);
dispatch_async(signUpQueue, ^{
// self.brain refers to a SignUpBrain property. See the code for the class below.
[self.brain signUpUsingEmail:email firstName:firstName lastName:lastName
andPassword:password];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:@"ShowMainFromSignUp" sender:self];
});
});
dispatch_release(signUpQueue);
}
Relevant code for the SignUpBrain:
- (void)signUpUsingEmail:(NSString *)email firstName:(NSString *)firstName
lastName:(NSString *)lastName andPassword:(NSString *)password {
self.email = email;
self.firstName = firstName;
self.lastName = lastName;
self.password = password;
// Handle sign-up web calls.
NSMutableURLRequest *signUpRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL
URLWithString:@"URL GOES HERE"]]; // obviously there's an actual URL in real code
NSString *postString = [NSString stringWithFormat:@"uname=%@&pw=%@&fname=%@&lname=%@",
email, password, firstName, lastName];
//NSLog(postString);
[signUpRequest setHTTPMethod:@"POST"];
[signUpRequest setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *signUpConnection =
[NSURLConnection connectionWithRequest:signUpRequest delegate:self];
[signUpConnection start];
// Store any user data.
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
self.signUpResponse = data;
NSError *error;
NSDictionary *jsonLoginResults = [NSJSONSerialization JSONObjectWithData:data
options:0 error:&error];
if (error) {
NSLog(error.description);
}
NSLog(jsonLoginResults.description);
// Return whether the user has successfully been registered.
// If success is 1, then registration has been completed successfully. 0 if not.
if ([jsonLoginResults objectForKey:@"status"]) {
NSLog(@"Success!");
}
}
I will also note that I created a test that uses these same web calls in a UIWebView, and it works successfully.
Please let me know if I need to clarify anything or include any more of the code! Thanks in advance.
Under normal circumstances, you don’t need to run networking operations on a different thread.
What you need to avoid is synchronous operation on the main thread. This will cause iOS to kill your application, since it will stop responding to events while the network transfer is busy.
Apple’s suggestion to is to use asynchronous operation on the main thread, rather than synchronous operation on another thread.
Using asynchronous operation will cause the networking itself to run in the background. You can consider this a dedicated thread by Apple. Your delegate methods will be called in the correct sequence on the main thread to catch up with networking as iOS determines it is appropriate.
Using NSURLConnection
Apple includes an example of how to use
NSURLConnectionin URL Loading System Programming Guide, Using NSURLConnection. It’s a short article, full of simple code examples. You should spend a few minutes to read this.In a nutshell, here’s the pattern:
NSMutableDatafor your response data.NSMutableDatainstance indidReceiveResponse. (You may receive multipledidReceiveResponseevents as you’re redirected, but you should only use data from the last one.)didReceiveDatato theNSMutableData. Don’t try to process it immediately; you will usually receive multipledidReceiveDataevents for a single transfer.connectionDidFinishLoading, your data is complete. Here, you can do something with it.When you have all the data, you can either send it to a different thread or (much simpler)
dispatch_asyncto a queue that isn’t running on the main thread. See Listing 5, “Example connectionDidFinishLoading: implementation” of URL Loading System Programming Guide.The idea here is that your start (or restart) accumulating data in
didReceiveResponse:, append data in yourdidReceiveData:, and actually do something with the data inconnectionDidFinishLoading:.It is possible to run an
NSURLConnectionon another thread, of course. That thread will need to be using a run loop to receive the delegate events from networking. But unless there’s some reason you need a separate thread, using asynchronous networking is Apple’s solution to this.Using
NSURLConnectionrequires a class member to accumulate data as its transferred. That means that if you’re going to have multiple simultaneous transfers, you’ll need something more complicated. Probably a wrapper class to driveNSURLConnection, and keep each response separate. By the time you’ve written this wrapper class, you’ve probably written a naive version ofAFHTTPRequestOperation, a part of AFNetworking.Assuming you only have a single transfer going on at once, the code looks a bit like this:
Using AFNetworking
Now that I’ve explained all this, I’m going to suggest an alternative. You might be better off using AFNetworking.
AFNetworking is an open source third party library. Its most basic classes wrap
NSURLConnectionin aNSOperation(thus, something that can be added to anNSOperationQueue). AFNetworking handles these events I’ve talked about automatically. Instead, you write a completion block. When you get this block, the transfer has either succeeded or failed. If it’s failed, the error is available on theAFHTTPRequestOperationinstance. If it’s succeeded, you can use the data on theAFHTTPRequestOperationinstance.(Note: I believe
AFHTTPRequestOperationactually does run the connection from another thread. However, it’s well-written and well-tested code. There’s nothing “wrong” with doing this, it’s just harder to pull off and usually unnecessary. But if your library does it for you, why not?)AFNetworking provides some HTTP logic that
NSURLConnectiondoesn’t. With AFNetworking, you’d just use aAFHTTPRequestOperationand set a completion block. All of the above becomes something like:In the past, I’ve written using NSURLConnection directly. But recently, I’ve been using AFNetworking more frequently. Unlike some other libraries, it’s not a radically different shape than NSURLConnection. In fact, it uses NSURLConnection, just wrapped in a NSOperation (which hopefully Apple will get to any release now). It’s worth considering, at least.