I am developing a login application. I have a registration view, I'm using sendAsynchronousRequest to send registration request.When I received a data back I want to show alert view displaying the registration if valid. Now my problem is When I received a data back the UI get blocked until alert view display.
why is that happen and how could I fix it ?
check the the code snippet:
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
error = connectionError;
NSMutableDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil ];
if ([dic count] == 1) {
[dic valueForKey:#"RegisterUserResult"];
UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:#"Registration" message:[dic valueForKey:#"RegisterUserResult"] delegate:self cancelButtonTitle:#"cancel" otherButtonTitles:nil];
[alertview show];
}else {
UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:#"Registration" message:[dic valueForKey:#"ErrorMessage"] delegate:self cancelButtonTitle:#"cancel" otherButtonTitles:nil];
[alertview show];
}
[self printData:data];
You try to perform UI operations on non-main thread because completion block executes on you custom operation queue.
You need to wrap all UI calls in
dispatch_async(dispatch_get_main_queue(), ^{
......
});
within custom operation queue non-main threads.
So, in your code any call for UIAlertView will looks like
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:#"Registration" message:[dic valueForKey:#"RegisterUserResult"] delegate:self cancelButtonTitle:#"cancel" otherButtonTitles:nil];
[alertview show];
});
If you have no heavy operations in your completion handler you can dispatch it on main thread directly setting queue to [NSOperationQueue mainQueue]
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {...}];
Related
I am performing JSON POST request by clicking UIButton and after the submission, segue perform can be done. So, After submitting values, I cannot perform POST request anymore. It shows status code 200 and response is OK. But, data is not reflected in the Backend. Here is my code:
(IBAction)transitsurveydone:(id)sender {
if([tempArray count]!=0){
/* alert= [[UIAlertView alloc] initWithTitle:#"Survey Submission"
message:#"Please Enter your location"
delegate:self
cancelButtonTitle:#"Modify"
otherButtonTitles:#"Submit",nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
alert.tag=2;
[alert show];*/
NSLog(#"Caption array is %# %#",captionArray,tempArray);
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:#"myURL"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSMutableDictionary *postDict = [[NSMutableDictionary alloc]init];
NSMutableDictionary *d=[[NSMutableDictionary alloc] initWithObjectsAndKeys:#10,#"\"Bus\"", nil];
NSMutableArray *m=[[NSMutableArray alloc] init];
NSString *str1,*str2,*str3,*str4;
// Checking the format
if(tempArray.count==1){
for (int x=0; x<1; x++) {
str1=[NSString stringWithFormat:#"\"%#\"",[captionArray objectAtIndex:x]];
[d setObject:[tempArray objectAtIndex:x] forKey:str1];
}
[request setHTTPBody:[[NSString stringWithFormat: #"{\n \"instance\" : %#,\n \"response_method\": \"web\",\n \"routes\": [\n {%#:%#}\n ]\n}",randomString,str1,[d objectForKey:str1] ]dataUsingEncoding:NSUTF8StringEncoding]];
}
NSLog(#"%#:%#",str1,[d objectForKey:str1]);
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// Handle error...
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSLog(#"Response HTTP Status code: %ld\n", (long)[(NSHTTPURLResponse *)response statusCode]);
NSLog(#"Response HTTP Headers:\n%#\n", [(NSHTTPURLResponse *)response allHeaderFields]);
}
NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response Body:\n%#\n", body);
}];
[task resume];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *respData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"~~~~~ Status code: %d", [response statusCode]);
if([response statusCode]==200){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:#"Transit Survey submitted" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alert.transform = CGAffineTransformMakeTranslation( 10, 740);
[alert show];
[self performSelector:#selector(dismissAlert:) withObject:alert afterDelay:1.0f];
submitteddonthide=NO;
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:#"Transit Survey Submission Failed" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alert.transform = CGAffineTransformMakeTranslation( 10, 740);
[alert show];
[self performSelector:#selector(dismissAlert:) withObject:alert afterDelay:1.0f];
}
if([prefs integerForKey:#"humandone"]==1){
[self performSegueWithIdentifier:#"transittohuman" sender:nil];
}
else{
[self performSegueWithIdentifier:#"gobackfromtransit" sender:nil];
}
}
`
The above code is in IBAction
Your code looks fine and there is no any issues.
If this issue is happens all the time, add "Advanced Rest Client" add-on to chrome browser and test your server URL passing respective input values. If this process also can't be able to update values on your backend there should be some issue on your backend.
A couple of thoughts:
You are using status code to determine whether the server inserted the data correctly or not. You should actually have your web service build an affirmative response (in JSON, would be great) that says whether data was successfully inserted or not. You should not be relying solely on the web server's request response code.
I would observe the request with something like Charles and make sure it looks OK.
You're building a JSON request manually, which is very fragile. I would suggest using Charles to observe the request, and copy and paste it into http://jsonlint.com and make sure it's OK.
Even better, use NSJSONSerialization which is more robust and easier to use.
Also, you're sending this request twice. Lose this second request (you shouldn't do synchronous requests, anyway) and put all of your confirmation logic inside the session's completion handler block.
Yes. After struggling a bit, Clearing cookies helped me a lot :-
Here is a chunk of code, which is pretty much simple
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [cookieStorage cookiesForURL:[NSURL URLWithString:urlString]];
for (NSHTTPCookie *cookie in cookies) {
NSLog(#"Deleting cookie for domain: %#", [cookie domain]);
[cookieStorage deleteCookie:cookie];
}
Thank you Mr. reddys and Rob for the suggestions
when i use sendAsynchronousRequest method at that time this method inner block will execute after outside code will execute?
__block NSDictionary *dict;
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
NSInteger httpStatus = [((NSHTTPURLResponse *)response) statusCode];
NSLog(#"httpStatus inside block:%d",httpStatus);
if ([data length]>0 && connectionError==nil)
{
dict=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:nil];
}
else if (connectionError)
{
UIAlertView *alt=[[UIAlertView alloc] initWithTitle:#"Error" message:[connectionError localizedDescription] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alt show];
}
}];
return dict;//it will return null because it will run before execute inner block of sendAsynchronousRequest
Asynchronous request always run in the network thread.
Your [NSOperationQueue mainQueue] means that the block-callback will schedule in mainQueue.
If you wanna to wait until the request finished, you can use synchronous request, or use semaphore to block current thread.
See
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
but attention that this way to request will block current thread. be careful when using this way.
You should use block method
First define a block
typedef void (^OnComplate) (NSDictionary * dict);
- (void)viewDidLoad{
[self getDataWithBlokc:^(NSDictionary *dict) {
NSLog(#"%#",dict);
}];
}
Use the following method
-(void)getDataWithBlokc:(OnComplate)block{
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
NSInteger httpStatus = [((NSHTTPURLResponse *)response) statusCode];
NSLog(#"httpStatus inside block:%d",httpStatus);
if ([data length]>0 && connectionError==nil)
{
NSDictionary* dict=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:nil];
block(dict);
}
else if (connectionError)
{
UIAlertView *alt=[[UIAlertView alloc] initWithTitle:#"Error" message:[connectionError localizedDescription] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alt show];
}
}];
}
So just like the question suggests, I'm trying not to freeze up the UI after the user sends some data to the server. In my case, they're possibly sending a lot of data and server side I have to do a foreach loop for multiple queries.
While all that is happening, I don't want the user to wait for a response so I'm dismissing the modal VC after "Send" is clicked. The data still gets inserted into the database but what if there's an error? Right now I'm showing a UIAlertView after the modal VC is dismissed but I get a bad access error. What's the best way of showing an error?
- (void)send:(id)sender{
if ([[Data sharedInstance].someData objectForKey:#"time"] != NULL) {
[self dismissViewControllerAnimated:YES completion:^(){
NSMutableDictionary *paramDic = [NSMutableDictionary new];
[paramDic setValue:[[Data sharedInstance].someData objectForKey:#"oneArray"] forKeyPath:#"oneArray"];
[paramDic setValue:[NSArray arrayWithObjects:[[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"Name"], [[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"State"], [[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"Country"], nil] forKeyPath:#"twoArray"];
[paramDic setValue:[[[Data sharedInstance].someData objectForKey:#"three"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKeyPath:#"three"];
[paramDic setValue:[[NSUserDefaults standardUserDefaults] valueForKey:#"username"] forKey:#"username"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:paramDic options:NSJSONWritingPrettyPrinted error:nil];
NSURL *url = [NSURL URLWithString:#"http://localhost/myapp/handleData.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSString *length = [NSString stringWithFormat:#"%d", jsonData.length];
[request setValue:length forHTTPHeaderField:#"Content-Length"];
[request setValue:#"json" forHTTPHeaderField:#"Data-Type"];
[request setHTTPBody:jsonData];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (![httpResponse statusCode] == 200 || ![[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] isEqualToString:#"success"]) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}
}];
}];
}
That's how I'm doing it now...what's a better way?
The above answer is valuable. But I think this is what causes the problem.
Change:
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
To:
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
Hope this helps.
I'm thinking the issue has to do with your nested completion blocks. Because you place your web service call in the modal dismissal completion block, by the time it finishes and starts to execute your URL completion block, the class/controller in charge of that block has already been forgotten/destroyed/deallocated/whatever. At the very least, the view no longer exists where you're trying to present your alert view. You've listed self as the delegate of that alert view, but self doesn't exist anymore.
Try this first: instead of trying to display your alert on a no-longer-in-memory view from a background thread (big no-no even if the view still existed), try posting a notification to the main thread that you can pick up elsewhere in your app (such as your root view controller) and present the alert from there:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (![httpResponse statusCode] == 200 || ![[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] isEqualToString:#"success"]) {
dispatch_async(dispatch_get_main_queue(),^{
// observe this notification in root view controller or somewhere
// that you know will be in memory when it fires
[[NSNotificationCenter defaultCenter] postNotificationName:#"ErrorAlert"
object:nil
userInfo:imageDict];
});
}
}];
If that still doesn't work because the app has discarded your completion block when the view was dismissed, you will need to create a separate class that is in charge of sending this web service request instead of doing it all directly in the completion block. But still, remember to present your alert on the main thread :)
I have this code:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ( error != nil )
{
// Display a message to the screen.
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"There was a server error getting your business plan. We use a remote server to backup your work."
message:#"Please make sure your phone is connected to the Internet. If the problem persists, please let us know about this."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
but it executes when a NSUrlConnection returns from the server. It creates a crash on rare occasions. Is it possible? It seems like such a harmless piece of code.
Thanks!
Is the NSURLConnection returning a result on some odd thread? I don't know, but I suspect that UIAlertView is only meant to work on the UI thread, since it starts with UI.
(dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"There was a server error getting your business plan. We use a remote server to backup your work."
message:#"Please make sure your phone is connected to the Internet. If the problem persists, please let us know about this."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
});)
Sorry, haven't compiled this, there's probably a typo in there somewhere.
If it's entering the conditional block to display the error it's not because of the UIAlert, it's because the NSURLConnection encountered an error. I would output to the console the error information so that you can see what the error is when it gets into these rare occasions and solve the issue with the NSURLConnection
The issue is that, you are not displaying the alertView in the main thread. All UI related code is need to be work on main thread.
I got a similar crash when I displayed the alertView on another thread.
You need to change the method like:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ( error != nil )
{
// Display a message to the screen.
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"There was a server error getting your business plan. We use a remote server to backup your work."
message:#"Please make sure your phone is connected to the Internet. If the problem persists, please let us know about this."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
});
}
}
or change it like:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ( error != nil )
{
// Display a message to the screen.
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"There was a server error getting your business plan. We use a remote server to backup your work."
message:#"Please make sure your phone is connected to the Internet. If the problem persists, please let us know about this."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message performSelectorOnMainThread:#selector(show) withObject:nil waitUntillDone:NO];
}
}
I am new ios, i use storyboard to implement list data function demo, include add data.
In my AddWrokLogViewController,I have a method (IBAction)done:(id)sender bind + button.
as follow, in my do function, I want to check user inputs and save user input datas by http post. when post data in remote save success, I want go back to list view.
as before I search apple article introduction storyboard, but in this demo, when user tap add button,direct transfer to list page view :http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/SecondiOSAppTutorial/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011318-CH1-SW1
If I use AFHTTPClient to save data in remote server success, then go back to list page view, or still in add page view and display error message.
any one can give me some suggestions, thanks!
NSString *url = [NSString stringWithFormat:#"%#",api_createworklog];
NSDictionary *params=[NSDictionary dictionaryWithObjectsAndKeys:uname,#"loginname",token,#"token",beginTime
,#"beginTime",content,#"content",address,#"address",projectNameTemp,#"projectName"
,workType,#"workType",workDate,#"workDate",validDate,#"validDate", nil];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
[client postPath:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *responseString = [operation responseString];
NSDictionary *data = [responseString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];
NSString *sysCode = [data objectForKey:#"syscode"];
NSString *businessCode = [data objectForKey:#"businesscode"];
if(sysCode != nil && ![sysCode isEqualToString:#""] && businessCode != nil && ![businessCode isEqualToString:#""]){
if([sysCode isEqualToString:ws_access_success] && [businessCode isEqualToString:ws_access_success]){
//[self.navigationController popViewControllerAnimated:YES];
addSuccess = true;
} else{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"添加失败" message:responseString delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSString *responseString = operation.responseString;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"访问服务器异常,添加失败!" message:responseString delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}];