Wait for NSURLSessionDataTask to come back - ios

I am new to Objective C and iOS development in general. I am trying to create an app that would make an http request and display the contents on a label.
When I started testing I noticed that the label was blank even though my logs showed that I had data back. Apparently this happens because the the response is not ready when the label text gets updated.
I put a loop on the top to fix this but I am almost sure there's got to be a better way to deal with this.
ViewController.m
- (IBAction)buttonSearch:(id)sender {
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: #"https://en.wiktionary.org/wiki/incredible"];
//I put this here to give some time for the url session to comeback.
int count;
while (http.responseText ==nil) {
self.outputLabel.text = [NSString stringWithFormat: #"Getting data %i ", count];
}
self.outputLabel.text = http.responseText;
}
HttpRequest.h
#import <Foundation/Foundation.h>
#interface HttpRequest : NSObject
#property (strong, nonatomic) NSString *responseText;
- (void) sendRequestFromURL: (NSString *) url;
- (NSString *) getElementBetweenText: (NSString *) start andText: (NSString *) end;
#end
HttpRequest.m
#implementation HttpRequest
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
}];
[task resume];
}
Thanks a lot for the help :)
Update
After reading a lot for the very useful comments here I realized that I was missing the whole point. So technically the NSURLSessionDataTask will add task to a queue that will make the call asynchronously and then I have to provide that call with a block of code I want to execute when the thread generated by the task has been completed.
Duncan thanks a lot for the response and the comments in the code. That helped me a lot to understand.
So I rewrote my procedures using the information provided. Note that they are a little verbose but, I wanted it like that understand the whole concept for now. (I am declaring a code block rather than nesting them)
HttpRequest.m
- (void) sendRequestFromURL: (NSString *) url
completion:(void (^)(NSString *, NSError *))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Create a block to handle the background thread in the dispatch method.
void (^runAfterCompletion)(void) = ^void (void) {
if (error) {
completionBlock (nil, error);
} else {
NSString *dataText = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
completionBlock(dataText, error);
}
};
//Dispatch the queue
dispatch_async(dispatch_get_main_queue(), runAfterCompletion);
}];
[task resume];
}
ViewController.m
- (IBAction)buttonSearch:(id)sender {
NSString *const myURL = #"https://en.wiktionary.org/wiki/incredible";
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: myURL
completion: ^(NSString *str, NSError *error) {
if (error) {
self.outputText.text = [error localizedDescription];
} else {
self.outputText.text = str;
}
}];
}
Please feel free to comment on my new code. Style, incorrect usage, incorrect flow; feedback is very important in this stage of learning so I can become a better developer :)
Again thanks a lot for the replies.

You know what, use AFNetworking to save your life.
Or just modify your HttpRequest's sendRequestFromURL:
- (void)sendRequestFromURL:(NSString *)url completion:(void(^)(NSString *str, NSError *error))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
completionBlock(nil, error);
} else {
completionBlock([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], error);
}
});
}];
[task resume];
}
and invoke like this
[http sendRequestFromURL:#"https://en.wiktionary.org/wiki/incredible" completion:^(NSString *str, NSError *error) {
if (!error) {
self.outputLabel.text = str;
}
}];

Rewrite your sendRequestFromURL function to take a completion block:
- (void) sendRequestFromURL: (NSString *) url
completion: (void (^)(void)) completion
{
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
if (completion != nil)
{
//The data task's completion block runs on a background thread
//by default, so invoke the completion handler on the main thread
//for safety
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
[task resume];
}
Then, when you call sendRequestFromURL, pass in the code you want to run when the request is ready as the completion block:
[self.sendRequestFromURL: #"http://www.someURL.com&blahblahblah",
completion: ^
{
//The code that you want to run when the data task is complete, using
//self.responseText
}];
//Do NOT expect the result to be ready here. It won't be.
The code above uses a completion block with no parameters because your code saved the response text to an instance variable. It would be more typical to pass the response data and the NSError as parameters to the completion block. See #Yahoho's answer for a version of sendRequestFromURL that takes a completion block with a result string and an NSError parameter).
(Note: I wrote the code above in the SO post editor. It probably has a few syntax errors, but it's intended as a guide, not code you can copy/paste into place. Objective-C block syntax is kinda nasty and I usually get it wrong the first time at least half the time.)

If you want easy way then Don't make separate class for call webservice. Just make meethod in viewController.m instead. I mean write sendRequestFromURL in your viewController.m and update your label's text in completion handler something like,
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
self.outputLabel.text = self.responseText;
})
}];
[task resume];
}

Related

How to return result of asynchronous json request

I have a method that returns a string usually locally, but with a backup from the Web. I was retrieving some JSON using dataWithContentsOfUrl but want to switch to using a Session object which is better for the UI and also--if I am not mistaken--allows the server to set a sessionId on the phone, however, I'm struggling with the async issue.
With the old code, I just returned the JSON but I'm struggling with how to do this for the asynchronous result. I can't change the calling method which returns a string. What can I do with the asynchronous Api call to use the data that is retrieved?
async:
-(void)getAsyncAnswerFor:(NSString*) str {
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSURL *url = [NSURL URLWithString:surl];
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//HOW DO I PASS THIS BACK TO THE CALLING METHOD OR IS THAT IMPOSSIBLE
}];
[downloadTask resume];
}
sync
-(NSString*)getAnswerFor:(NSString*) str {
NSError *error;
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString:surl]];
NSMutableArray *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
//process JSON
if (error) {
return #"";
}
return #"processed JSON";
}
Would appreciate any suggestions.
If what I want to do is totally impossible, is it possible to set a sessionID on the phone without the Session object? I know setting a session ID is is not the greatest approach, but I'm trying to avoid a lot of authentication overhead.
You can pass a block to your asynchronous function and then call it when the url session completion handler is called. This is a trivial example:
- (void)doSomethingWithBlock:(void (^)(double, double))block {
...
block(21.0, 2.0);
}
I lifted this ^^ from the Apple Docs but you might be able to do something like this: (Note: I didn't check this in a compiler!)
-(void)getAsyncAnswerFor:(NSString*) str completion:(void (^)(NSData, NSURLResponse, NSError))block {
NSString *surl = [NSString stringWithFormat: #"https://~.com//api.php?q=%#",str];
NSURL *url = [NSURL URLWithString:surl];
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data, response, error);
}];
[downloadTask resume];
}
You'll need to be careful if you try to reference self anywhere in the blocks.

How to get result in blocks immediately?

I'm using blocks to get header fields from response in one class and I have to get that in another class.
I implemented code like this
In first class:
- (void)viewDidLoad {
[super viewDidLoad];
UserAuthentication *auth = [[UserAuthentication alloc]init];
NSDictionary *dict = [auth getUserConfiguration];
NSLog(#"%#",dict);
}
In userAuthentication class:
-(NSDictionary *)getUserConfiguration;
{
__block NSDictionary *resultDictionary;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://72.52.65.142:8083/auth"]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if ([response respondsToSelector:#selector(allHeaderFields)]) {
resultDictionary = [httpResponse allHeaderFields];
NSLog(#"%#",resultDictionary);
}
}] resume];
NSLog(#"%#",resultDictionary);
return resultDictionary;
}
Here my problem is in first class I'm getting dict as null.
Even in userAuthentication class also I'm getting null.
But after some time call back method is calling and then I can see the response correctly in completionHandler.
So how I can get response in firstClass?
You are misunderstanding the basic principle of async operation that runs in background thread and when the operation is completed it gives you data in completion block.
To get response in viewDidLoad Method of second class you need to use blocks. like below
-(void)getUserConfigurationOnCompletion:(void (^)(NSDictionary *))completion
{
__block NSDictionary *resultDictionary;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://72.52.65.142:8083/auth"]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if ([response respondsToSelector:#selector(allHeaderFields)]) {
resultDictionary = [httpResponse allHeaderFields];
// Call completion with parameter
completion(resultDictionary);
}
}] resume];
}
and use it like this in viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
UserAuthentication *auth = [[UserAuthentication alloc]init];
[auth getUserConfigurationOnCompletion:^(NSDictionary *dict){
// do necessary work with response dictionary here
NSLog(#"%#",dict);
}];
}
That's something you'll have to get used to: Anything that is related to internet access (and some things not related to it) cannot be returned immediately - unless you are willing to wait for it, block your user interface, and make your users very, very unhappy.
You have to write your application in such a way that it can be in four states: Never asked for the user configuration, asking for the user configuration, having asked for and received the user configuration, or having asked for the user configuration and failed. In this case your view must handle all four possibilities and must handle when the situation changes.
You are using NSURLSession! It performs tasks on a background thread!
Completion block is called only when you get the response from the server. Naturally it will take time to complete the request. You should use blocks to complete the request and return the result on completion.
-(void)getUserConfigurationAndOnCompletion:(void(ˆ)(NSDictionary *dict, NSError *error))completion;
{
__block NSDictionary *resultDictionary;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://72.52.65.142:8083/auth"]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if ([response respondsToSelector:#selector(allHeaderFields)]) {
resultDictionary = [httpResponse allHeaderFields];
NSLog(#"%#",resultDictionary);
//This will call the block in the first class with the result dictionary
dispatch_async(dispatch_get_main_queue(), ^{
if(!error){
completion(resultDictionary,nil);
}else{
completion(nil,error);
}
});
}] resume];
}
When you call the above code from your first class, it will create a block there and you will get the required dictionary over there in the block parameter!
Your method should be like,
-(void)getUserConfigurationwithCompletionHandler : (void (^)(NSDictionary* resultDictionary))completionHandler
{
__block NSDictionary *resultDictionary;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://72.52.65.142:8083/auth"]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if ([response respondsToSelector:#selector(allHeaderFields)]) {
resultDictionary = [httpResponse allHeaderFields];
NSLog(#"%#",resultDictionary);
completionHandler(resultDictionary);
}
}] resume];
NSLog(#"%#",resultDictionary);
}
and you can access it like,
- (void)viewDidLoad {
[super viewDidLoad];
[self getUserConfigurationwithCompletionHandler:^(NSDictionary *resultDictionary) {
// you can acess result dictionary here
NSLog(#"%#",resultDictionary);
}];
}
because you will getting data in response of webservice(from server) so it takes some time to complete so you have to return data from completion handler of webservice call and you can't return data from completion handler so you have to create own completion handler and call as i have mentioned above. you can access resultDictionary in completionHandler and you can show new VC from this completionHandler.
You have to call a method in your first class in your completionHandler.
Create a property of type YOURFIRSTCLASS *myfirstclass in your UserAuthentication Class.
Pass your firstclass with "self" to the UserAuthentication object.
create visible method in your firstclass "-(void)responseCaller:(NSDictionary)dict"
call the method in your response method
YOURFIRSTCLASS .h:
-(void)responseCaller:(NSDictionary)dict;
YOURFIRSTCLASS .m
-(void)responseCaller:(NSDictionary)dict
{NSLog(#"%#",dict);}
- (void)viewDidLoad {
[super viewDidLoad];
UserAuthentication *auth = [[UserAuthentication alloc]init];
auth.myfirstclass = self;
NSDictionary *dict = [auth getUserConfiguration];
NSLog(#"%#",dict);
}
UserAuthentication .h
#import "YOURFIRSTCLASS.h"
#property (nonatomic) *myfirstclass;
UserAuthentication .m
-(NSDictionary *)getUserConfiguration;
{
__block NSDictionary *resultDictionary;
NSURLSession *session = [NSURLSession sharedSession];
__weak myfirstclassSave = myfirstclass;
[[session dataTaskWithURL:[NSURL URLWithString:#"http://72.52.65.142:8083/auth"]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if ([response respondsToSelector:#selector(allHeaderFields)]) {
resultDictionary = [httpResponse allHeaderFields];
[myfirstclassSave responseCaller:resultDictionary ];
}
}] resume];
return resultDictionary;
}
Something like that

Get image URL from webpage

I am trying to get the image from this website, but I am having a hard time. All I need is the URL of the image, when I print the URL I get noting back. This is the URL and I am trying to get the main image seen in the middle, http://theoldrussuanbum.vsco.co/media/555722fde555153e3e8b4591
I have been trying the following code with no luck.
NSURL *URL = [NSURL URLWithString:_urlTextField.text];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:URL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *contentType = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
contentType = headers[#"Content-Type"];
}
HTMLDocument *document = [HTMLDocument documentWithData:data
contentTypeHeader:contentType];
HTMLElement *element = [document firstNodeMatchingSelector:#"img"];
NSString *urlString = element.attributes[#"src"];
NSLog(#"URL: %#", urlString);
}] resume];
Can anyone help?
You can use regular expressions to first
find the start with the regular expression: #"twitter:image\"\\s+content=\""
and then
extract the URL with the regular expression: #"[^>]+"
You can find information of regular expression syntax at the ICU User Guide: Regular Expressions.
Example code:
NSString *urlString = #"http://theoldrussuanbum.vsco.co/media/555722fde555153e3e8b4591";
NSURL *URL = [NSURL URLWithString:urlString];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *sessionTask;
sessionTask = [session dataTaskWithURL:URL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSRange preambleRange = [html rangeOfString:#"twitter:image\"\\s+content=\"" options:NSRegularExpressionSearch];
if (preambleRange.location != NSNotFound) {
NSString *htmlOffset = [html substringFromIndex:preambleRange.location + preambleRange.length];
NSRange imgUrlRange = [htmlOffset rangeOfString:#"[^>]+" options:NSRegularExpressionSearch];
if (imgUrlRange.location != NSNotFound) {
NSString *imgURLString = [htmlOffset substringWithRange:imgUrlRange];
NSLog(#"URL: %#", imgURLString);
}
}
}];
[sessionTask resume];
Output:
URL: http://image.vsco.co/1/548c6a64020e11517164/555722fde555153e3e8b4591/600x800/vsco_051615.jpg"
Of course production code must handle all errors which this example code does not.
I think first thing you need to do is provide actual url of image resource.
For example; http://image.vsco.co/1/548c6a64020e11517164/548c7b532b5615b6658b4567/vsco_121314.jpg
This is what I get from page source of that web page
And then just refer this question;
iOS download and save image inside app

UIKeyboardTaskQueue threading issue

I'm fairly new to iOS development and I've been stuck on this bug for a while. I'm making a simple app the uses a web service. Right now I currently have two view controllers. A login view controller (with its NIB file) and a main view controller (with its NIB file). When I created the app I chose an empty application so I don't have a storyboard. Instead I'm using UINavigationController. When I run my code I get the following error after entering my username and password and pressing submit in the login view:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] may only be called from the main thread.'
This is the code I have for my submit button:
-(IBAction)logIn:(id)sender{
UIApplication *application = [UIApplication sharedApplication];
application.networkActivityIndicatorVisible = YES;
[_loginNetworkingContorller checkCredentialsWithUsername:self.username.text withPassword:self.password.text completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"SUCESS");
NSDictionary *credentials = #{self.username.text: self.password.text};
[KeychainUserPass save:#"MY APP" data:credentials];
UIViewController *mainView = [[RDMainViewController alloc] initWithNibName:#"RDMainViewController" bundle:nil];
[self.navigationController pushViewController:mainView animated:YES];
}
else{
NSLog(#"ERROR");
}
}
else{
NSLog(#"ERROR");
}
}];
}
And here is the code for the following function
[_loginNetworkingContorller checkCredentialsWithUsername:self.username.text withPassword:self.password.text completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
-(void)checkCredentialsWithUsername:(NSString *)username withPassword:(NSString *)password completionHandler:(void (^)(NSData *data,NSURLResponse *response, NSError *error))myCompletion
{
NSString *requestString = #"SOME WEBSITE";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSData *userPasswordData = [[NSString stringWithFormat:#"%#:%#", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:#"Basic %#", base64EncodedCredential];
NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPAdditionalHeaders=#{#"Authorization":authString};
self.session=[NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
myCompletion(data, response, error);
}];
[dataTask resume];
}
I'm a stuck right now and am not really sure what the issue is especially since I don't do anything with the keyboard. I have a feeling there is an issue with my blocks but I'm not exactly sure what the issue is. Any help would be greatly appreciated.
Hey I had this same issue after I received my response from my web call. I was able to solve it be doing the following:
//do something with response
dispatch_async(dispatch_get_main_queue()) { () -> Void in
// continue with program by calling next step on main thread
}
I think if you push to the next view controller iOS attempts to do it on not the main thread, causing the error, but I'm not 100% is that is accurate

Block not completing in iOS

I'm relatively new to iOS development but I'm working on an application to get a better understanding of development. I'm working with a web service and want to check the credentials a user enters. To do this I am making a simple get request with their credentials and then checking the http status for 200. Here is my code below:
-(BOOL)checkCredentials:(NSString *)username withPassword:(NSString *)password{
NSString *requestString = #"SOME URL";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSData *userPasswordData = [[NSString stringWithFormat:#"%#:%#", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:#"Basic %#", base64EncodedCredential];
NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPAdditionalHeaders=#{#"Authorization":authString};
self.session=[NSURLSession sessionWithConfiguration:sessionConfig];
__block BOOL success = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
success = YES;
}
}
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[dataTask resume];
return success;
}
I was going to use a semaphore to wait for the block to complete so I can check the status code and then return. But first it seems like my code just hangs, and I think that because I don't have a release, but that's not allowed with ARC. I'm not sure why it's hanging. Is there a better way to wait for the block to complete (without a semaphore) so I can return whether my credentials are valid?
Also is there a better way to pass the username and password so that it's not possible for someone to spoof the username and password?
Any help would be greatly appreciated.
Think simple!
Make your own completionHandler so that you won't deal with the return anymore, the caller will take the responsibility of result verification instead.
There's one thing you need to keep in mind, that if you want to modify anything related to UI (User Interface), you need to dispatch your completion block to main queue or you will get unexpected behavior, see more detail here.
Change your return type to void and add a completion block:
-(void)checkCredentials:(NSString *)username withPassword:(NSString *)password completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))myCompletion
{
NSString *requestString = #"http://google.com";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Here you return exactly what the NSURLSessionDataTask downloaded
// and pass it to the caller as an another completion block
myCompletion(data, response, error);
}];
[dataTask resume];
}
Caller's code, I assume that self is the caller:
[self checkCredentials:#"" withPassword:#"" completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
// Result verification's here
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"SUCESS");
}
}
}];
You code stops waiting for a semaphore and [dataTask resume] is never executed.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); <=== waits here
[dataTask resume]; <=== never reached
I'd suggest not using the semaphore here. Do the work in your block instead.
As to username/password. If you worry about spoofing then SSL layer on top of HTTP is the answer.
This is a really dangerous pattern, because this call is going to block until the network request completes. If this is on the main thread, your app will stop responding and the watchdog may kill you.
That warning aside, the reason the block doesn't complete is because the network task is never started. You trap on your semaphore before you call resume, so your task never runs. I would also, personally use a dispatch_group to do the waiting.
To make it better, you would need to rewrite it asynchronously. Basically have your app continue to function, maybe disable the inputs, until the call completes, then run a block to re-enable them, or show an error:
// Assume your login button and whatever are exposed as properties here
self.loginButton.enabled = NO;
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
success = YES;
}
}
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
// Need to be back on the main queue, the call is complete
self.loginButton.enabled = YES;
}];
[dataTask resume];
Or, just to keep it the way you have it, but resolve the immediate issue, re-order your trap so that it happens after the task resumes:
[dataTask resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // might want to time out here instead of waiting forever
return success;

Resources