I am trying to use threads in my app to call web service and get data from
connectionDidFinishLoading
delegate method, but non-main thread does not wait for delegate methods to finish.
here is code for thread :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Here your non-main thread.
[self Login_WS];
//sleep(10);
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
//[MMProgressHUD updateProgress:1.f];
[MMProgressHUD dismissWithSuccess:#"done"];
DetailOfMenu *detail = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
//[colorVC setColor:color];
UINavigationController *colorNavi1 = [[UINavigationController alloc] initWithRootViewController:detail];
[self.qp_splitViewController setRightController:colorNavi1];
MenuTableView *colorVC = [self.storyboard instantiateViewControllerWithIdentifier:#"menu"];
//[colorVC setColor:color];
UINavigationController *colorNavi = [[UINavigationController alloc] initWithRootViewController:colorVC];
[self.qp_splitViewController setLeftController:colorNavi];
});
});
and here is code for web service :
-(void)Login_WS{
flag =1;
NSString* ENC_UserName = [self ENC:Username.text];
NSString* ENC_Password = [self ENC:Password.text];
NSString *envelopeText = [NSString stringWithFormat: #"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">\n"
"<soap12:Body>\n"
"<GetReportsNames xmlns=\"http://tempuri.org/\">\n"
"</GetReportsNames>\n"
"</soap12:Body>\n"
"</soap12:Envelope>\n",];
//envelopeText = [NSString stringWithFormat:envelopeText, txt1.text];
NSData *envelope = [envelopeText dataUsingEncoding:NSUTF8StringEncoding];
//NSLog(#"URL in Call_WS in SplashScreen %#",Var.url);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[request addValue:#"http://tempuri.org/GetReportsNames" forHTTPHeaderField:#"SOAPAction"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:envelope];
[request setValue:#"application/soap+xml; charset=utf-8"
forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [envelope length]]forHTTPHeaderField:#"Content-Length"];
// fire away
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection)
responseData = [NSMutableData data];
else
NSLog(#"NSURLConnection initWithRequest: Failed to return a connection.");
}
note: it is working fine in non-threading mood.
You should do the UI part that is currently after you call [self Login_WS]; when the web service call returns a response or a failure.
As the webservice call is also async the Login_WS method returns immediately after you create it, so your UI code will get called there. Instead implement the NSURLConnectionDelegate protocol and update your UI in:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
or:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
Not sure exactly at what point in code you want to wait and what part you want to prevent for getting executed. You issue is of synchronization category where one thread has to wait till other part finishes work. For this you can use NSCondition. You will find good examples on google for this :-)
Related
I am a beginer in iOS programming. I have some problem with NSURLConnection: I have installed SWRevealViewController https://github.com/John-Lluch/SWRevealViewController and when my app is loading Data from server, I can't use interaction with screen. I can't open my SWR-menu while Data is loading.
Here is my SWR in viewDidLoad:
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController ) {
[self.openMenyItmet setTarget: self.revealViewController];
[self.openMenyItmet setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
After that, I called Get method in viewDidLoad:
[self GetQUIZ];
Method detail:
- (void)GetQUIZ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *url = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
[request setURL:[NSURL URLWithString: url]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:[UIDevice currentDevice].name forHTTPHeaderField:#"device"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Left menu, User details: %#", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSLog(#"%#", [request allHTTPHeaderFields]);
if(conn) {
NSLog(#"Connection Successful");
} else
NSLog(#"Connection could not be made");
And then I use data in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *deserr = nil;
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseData options: 0 error: &deserr];
I read that i should use async methods, but I never use it before. Would you write some detail solution ?
Maybe, does have different path?
I would be very grateful for the help!
I'd suggest starting with NSURLSession, which is a modern API that will accomplish the same thing, asynchronously.
To use NSURLSession, you need a few piece of the puzzle:
A web address to reach, and optionally any payload or custom headers.
An instance of NSURL: where you're downloading from and an NSURLRequest to wrap it in.
An NSURLSessionConfiguration, which handles things like caching, credentials and timeouts.
The session itself.
You need an NSURLSessionTask instance. This is the closest object to your NSURLConnection. It has callbacks via delegate or a completion block, if you just need to know when it finishes.
Here's how this would look in code:
// 1. The web address & headers
NSString *webAddress = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
NSDictionary <NSString *, NSString *> *headers = #{
#"device" : [UIDevice currentDevice].name,
#"Content-Type" : #"application/x-www-form-urlencoded"
};
// 2. An NSURL wrapped in an NSURLRequest
NSURL* url = [NSURL URLWithString:webAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3. An NSURLSession Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setHTTPAdditionalHeaders:headers];
// 4. The URLSession itself.
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 5. A session task: NSURLSessionDataTask or NSURLSessionDownloadTask
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 5b. Set the delegate if you did not use the completion handler initializer
// urlSession.delegate = self;
// 6. Finally, call resume on your task.
[dataTask resume];
This will run asynchronously, allowing your UI to remain responsive as your app loads data.
When you send a request on the main thread, like you are doing now, your UI, which is always performed on the main thread, is blocked, waiting for the request to finish and process. So you should perform all your network on a background thread, asynchronously. I would recommend first to check the networking library AFNetworking , it could simplify most of your networking problems.
Welcome to SO. You should know that NSURLConnection was deprecated in iOS 9. You should be using NSURLSession instead. The approach is very similar. You can take the NSURLRequest you've created and pass it to the sharedSession object, which is set up for async requests. The simplest way to deal with it is to use the call dataTaskWithRequest:completionHandler:, which takes a completion block. In your completion block you provide code that handles both success and failure.
I have a project I am working on that has a number of different news feeds and announcement boards that displays post from various sources. Currently I have the code for the like, delete and flag buttons in methods contained in each class file for the views that display the feeds. I have been trying to craft a utility class that allows me to place the code for the three functionalities listed above in one object to be used throughout the project. I have done the exact same type of thing in C++ or Java, but am having issues reproducing it in objective-c. The like, delete and flag buttons use the NSURL libraries to interact with the web service. Bellow is an example of one of the methods I am trying to implement in the utility class, and is the code used to be implemented in the like buttons:
+ (void)btnLikeAction:(UIButton *)btnLike userIdString:(NSString *)userId contentSource:(NSString *)sourceId cellUsed:(NewsfeedCell *)cell dataForCell:(Post *)post
{
BOOL hasLiked = post.hasLiked;
UILabel *likeLabel = cell.likeCount;
NSString *pId = post.postId;
if (hasLiked)
{
[btnLike setEnabled:NO];
NSString *url = [NSString stringWithFormat: #"http://192.155.94.183/api/v1/likes/unlike/%#/%#/%#/", sourceId, pId, userId];
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
int localCount = [likeLabel.text intValue];
localCount--;
likeLabel.text = [NSString stringWithFormat:#"%i", localCount];
post.likeCount = [NSString stringWithFormat:#"%i", localCount];
post.hasLiked = NO;
[btnLike setEnabled:YES];
}
else
{
[btnLike setEnabled:NO];
NSError *err = nil;
NSDictionary *likeData = [NSDictionary dictionaryWithObjectsAndKeys:
userId, #"user_id",
pId, #"source_id",
sourceId, #"source",
nil];
NSData *JSONLike = [NSJSONSerialization dataWithJSONObject:likeData options:NSJSONWritingPrettyPrinted error:&err];
NSString *url = #"http://192.155.94.183/api/v1/likes.json";
NSURL *URL = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%d", [JSONLike length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:JSONLike];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
int localCount = [likeLabel.text intValue];
localCount++;
likeLabel.text = [NSString stringWithFormat:#"%i", localCount];
post.likeCount = [NSString stringWithFormat:#"%i", localCount];
post.hasLiked = YES;
[btnLike setEnabled:YES];
}
}
This code uses a web service to update the number of likes for a specific piece of content. It works when the method is placed into the individual ViewController class files, but when I try to make a utility class with the individual methods I run into issues with the didReceiveAuthenticationChallenge, didReceiveResponse, didReceiveData and connectionDidFinishLoading methods not being called. Originally, I assumed that the delegate methods would be called in the file that that the utility methods were called in. But that was not the case. When I implemented the method definitions in the actual utility class, the methods still weren't called. I did some research on the topic and looked into this article but found I was unable to find substantial resources that helped my specific situation. How do I set up my utility class? I can post the full code of the utility if needed.
As #serrrgi already said, the problem is that btnLikeAction:... is a class method, so that self is the class itself. You have the following options:
Make all delegate methods class methods, e.g.
+ (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"didReceiveResponse");
}
Create an instance of your Utility class and use that as delegate:
YourClass *u = [[self alloc] init];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:u];
Use sendAsynchronousRequest:..., which does not need a delegate:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil) {
NSLog(#"success");
} else {
NSLog(#"error: %#", error);
}
}];
I have a login screen and when a button is pressed this code executes:
- (IBAction)btnLogin:(id)sender {
[username resignFirstResponder];
[password resignFirstResponder];
Auth *authRequest = [[Auth alloc] init];
NSURL *url = [[NSURL alloc] initWithScheme:#"http" host:APIHOST path:#"/authenticate"];
[authRequest setUrl:url];
[authRequest setUsername:[username text]];
[authRequest setPassword:[password text]];
[authRequest authenticate];
[self performSegueWithIdentifier:#"Login2Tab" sender:self];
}
This works well and the segue executes as expected (So I know the segue exists and its identified correctly in the storyboard). I however don't want to perform the segue until the connection has finished loading so this implementation file is my delegate for NSURLConnection and at the bottom I have (and the if/else on responseCode does work)..:
// Close the connection
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
if(responseCode == 200) {
[self performSegueWithIdentifier:#"Login2Tab" sender:self];
} else {
NSLog(#"Bad.. bad.. bad...");
}
NSLog(#"Connection Closed.");
}
(When I put the performSegueWithIdentifer in the connectionDidFinishLoading I comment out the performSegueWithIdentier from above...).
But now the app always crashes stating:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (LoginViewController: 0x7163640) has no segue with identifier 'Login2Tab''
Which is where I am stuck because it works when its not called from within the connectionDidFinishLoading...
Just wanted to say I have this resolved altho I am not entirely sure how.
Basically this line in my ViewController
[authRequest authenticate];
Was loading a method in an external class file:
- (void)authenticate {
NSURL *url = [[NSURL alloc] initWithScheme:#"http" host:APIHOST path:#"/authenticate"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:3];
NSString *postString = [[NSString alloc] initWithFormat:#"email=%#&password=%#", [username text], [password text]];
[request setValue:APIKEY forHTTPHeaderField:#"apikey"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
[request setURL:url];
if ([NSURLConnection canHandleRequest:request]) {
NSLog(#"Starting network connection");
NSURLConnection *myConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[myConnection start];
} else {
//Error cannot activate network request from device
}
}
The delegates for the NSURLConnection were in the viewcontroller... and I could see them being accessed when the method was complete however I couldn't perform the segue from them. By moving this function from the class file to my viewcontroller I was able to call my segue's from the delegate method of connectionDidFinishLoading....
Strange but worked.
Ok, this seems like it should be very simple - All I want to do is call my ServerConnect.m (NSObject), NSURL Connection Request Method, from my SignIn.m (ViewController) and stop the UIActivityIndicatorView after the NSURL Request has completed. Of course, if I do it all on the main thread:
- (IBAction)forgotPassword:(id)sender {
[activityIndicator startAnimating];
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[activityIndicator stopAnimating];
}
Then, everything will then execute concurrently, and the activity indicator will start and stop before the connection method finishes...
Thus, I attempted to place the connection request on a secondary thread:
- (IBAction)forgotPassword:(id)sender {
[NSThread detachNewThreadSelector: #selector(requestNewPassword:) toTarget:self withObject:userEmail.text];
}
- (void) requestNewPassword:(NSString *)email
{
[self->thinkingIndicator performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];
//Make NSURL Connection to server on secondary thread
NSString *securityID = [[NSString alloc] init];
securityID = #"security";
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[self->thinkingIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
But, I don't see the activity indicators here either, which may be due the NSURL Request not functioning properly on the secondary thread (i.e. for some reason, it does not gather an xml string as it does when requested on the main thread).
What is the proper way to architecture my code to make this work? I am surprised at how much work has been involved in trying to figure out how to get my activity indicator to simply stop after a method from another file has finished executing. Is there a way to run the code in series (one after another) and not concurrently? Any help would be appreciated.
Updated to Show: sendUserPassword:(NSString *)withSecurity:(NSString *)
- (void)sendUserPassword:(NSString *)emailString
withSecurity:(NSString *)passCode;
{
NSLog(#"Making request for user's password");
newUser = NO;
fbUser = NO;
forgotPassword = YES;
NSString *post = [NSString stringWithFormat: #"email=%#&s=%#", emailString, passCode];
NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding];
//Construct the web service URL
NSURL *url = [NSURL URLWithString:#"http://www.someurl.php"];
//Create a request object with that URL
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:90];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
//Clear out the existing connection if there is one
if(connectionInProgress) {
[connectionInProgress cancel];
}
//Instantiate the object to hold all incoming data
xmlData = [[NSMutableData alloc] init];
//Create and initiate the conection - non-blocking
connectionInProgress = [[NSURLConnection alloc] initWithRequest: request
delegate:self
startImmediately:YES];
}
One suggestion try like this:
- (IBAction)forgotPassword:(id)sender
{
[self->thinkingIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(requestNewPassword:) toTarget:self withObject:userEmail.text];
}
- (void) requestNewPassword:(NSString *)email
{
//Make NSURL Connection to server on secondary thread
NSString *securityID = [[NSString alloc] init];
securityID = #"security";
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[self->thinkingIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
I ended up incorporating the NSNotification system (see Multithreading for iOS) to solve my problem. Any reason why this would be frowned upon:
"One easy way to send updates from one part of your code to another is Apple’s built-in NSNotification system.
It’s quite simple. You get the NSNotificationCenter singleton (via [NSNotificationCenter defaultCenter]) and:
1.) If you have an update you want to send, you call postNotificationName. You just give it a unique string you make up (such as “com.razeware.imagegrabber.imageupdated”) and an object (such as the ImageInfo that just finished downloading its image).
2.) If you want to find out when this update happens, you call addObserver:selector:name:object. In our case the ImageListViewController will want to know when this happens so it can reload the appropriate table view cell. A good spot to put this is in viewDidLoad.
3.) Don’t forget to call removeObserver:name:object when the view gets unloaded. Otherwise, the notification system might try to call a method on an unloaded view (or worse an unallocated object), which would be a bad thing!"
You could try something this, it uses a block when it is finished. I had similar thing right here.
// Turn indicator on
// Setup the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 90.0];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Its has finished but sort out the result (test for data and HTTP 200 i.e. not 404)
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (data != nil && error == nil && [httpResponse statusCode] == 200)
{
// Connection finished a gooden
// Do whatever you like with data
// Stop indicator
}
else
{
// There was an error, alert the user
// Do whatever you like with data
// Stop indicator
}
}];
so I want my app not to lock the GUI while sending an http request and getting the response, I made an attempt, but it complains that i use uikit outside of the mainthread, can someone please tell me the proper way of separating the http and the gui?
-(void)parseCode:(NSString*)title{
UIActivityIndicatorView *spinner;
spinner.center = theDelegate.window.center;
spinner.tag = 12;
[theDelegate.window addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t netQueue = dispatch_queue_create("com.david.netqueue", 0);
dispatch_async(netQueue, ^{
NSString *url =[NSString stringWithFormat:#"http://myWebService.org/"];
// Setup request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"POST"];
NSString *contentType = [NSString stringWithFormat:#"application/x-www-form-urlencoded"];
[request addValue:contentType forHTTPHeaderField:#"Content-Type"];
NSMutableString *data = [[NSMutableString alloc] init];
[data appendFormat:#"lang=%#", #"English"];
[data appendFormat:#"&code=%#", theDelegate.myView.text ];
[data appendFormat:#"&private=True" ];
[request setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]];
NSHTTPURLResponse *urlResponse = nil;
NSError *error = [[NSError alloc] init];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&urlResponse
error:&error];
NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[spinner removeFromSuperview];
[self presentResults:result];
});
});
}
Instead of using NSURLConnection:sendSynchronousRequest, use NSURLConnection:initWithRequest:delegate:startImmediately:, which sends the request asynchronously. Use the NSURLConnection:connectionDidFinishLoading delegate method to handle the response.
Apple provides an example in the URL Loading System Programming Guide.
If you set startImmediately to YES, the delegate method will be called on the same run loop as the one where the request is called from. Most likely, this will be your main run loop, so you can modify the UI all you want in the delegate method, without worrying about threading issues.
i didn't look to much into it but you can always try,
[self performSelectorInBackground:#selector(parseCode:) withObject: title];
that method causes the function to run on a separate thread in the back ground, and takes little effort to implement, i use it for when i'm doing simple downloads like
[NSData dataWithContentsOfURL:url]; but if you are doing something bigger you may need to do a little more work.
if you need to call the method out side of a class, then you will have to make a method with in the class that makes the call above that will then invoke the selector
I don't think the problem is with your HTTP code - it's with your access of the UI from within the background thread. Specifically this line:
[data appendFormat:#"&code=%#", theDelegate.myView.text ];
You're assumedly accessing a UITextView or something similar there. You need to do that outside the background thread. Move it into a local NSString variable and then you can safely access that variable from within the background thread.