ios - can showing a message cause a crash? - ios

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];
}
}

Related

NSURLSession crash with JSON data parameter is nil while error is managed

This problem has already been mentioned elsewhere but the solution was dealing with error. And here I think I manage my error correctly (but it sound like I am mistaken...).
I've got something strange I cannot figure out. My app crashes when I check if a server is not responding (offline) and not by getting back a JSON object. In my memories this was working before I dealt with iOS 9.2.
Using exception breakpoint, I can see it has to do with a data parameter which is "nil" when creating a dictionary (which is normal I guess since the server is offline and not returning anything) but the crash should be avoided by managing the error... which does not seem to be the case in my code.
A few lines:
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; // url with php script have been set before...
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[noteDataString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *dataRaw, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:dataRaw
options:kNilOptions error:&error];
// I thought this condition below should manage the error when the json dictionary cannot be set because of the nil "dataRay" paramater but my app crashes.
if (error) {
UIAlertView * av = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"Checking permission:\nGot error %#.\n", error] message:nil delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[av show];
// (I know UIAlertView is deprecated in iOS 9.0 :-)...)
}
NSString *status = json[#"status"];
if([status isEqual:#"1"]){
//Success in php script
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"It works" message:nil delegate:self cancelButtonTitle:#"Cool" otherButtonTitles:nil, nil];
[av show];
}
It sounds like the if (error) condition does not prevent from crashing...
Anyone could help me?
Thanks!
You should just add a if block after before initizaling your JSON. Such as:
if(rawData != nil) {
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:dataRaw
options:kNilOptions error:&error];
}
After that you can handle your error if it is not nil.

how to display UIAlertView inside sendAsynchronousRequest's completionHandler?

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) {...}];

Delayed Response when NSURLConnection is used with sendAsynchronousRequest

Correct me if I am wrong. please easy go on me. I am not expert in iOS.
I tried sending HTTP Request with two approaches.
1)
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
The response is so quick, the time delay to get response is constant. One additional effort is to handle the response data with delegate mechanism.
Hence I approached with (2)
2).
[NSURLConnection sendAsynchronousRequest:theRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSString *strData = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog (#" THE RESPONSE DATA IS %#", strData);
UIAlertView *alView = [[UIAlertView alloc] initWithTitle:#"Process Done" message:#"MY Message" delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
[alView show];
NSMutableData *mData = [NSMutableData dataWithData:data];
[self parseXML:mData]; // XML Parsing.
}];
With the (2) approach, I could see inconsistent delay of getting response.
My Understanding
Since I queue the block in NSOperationQueue, it is not guaranteed to be executed immediately.
If my understanding is correct, then what is the correct approach so, that the queued operation is handled immediately.

Displaying error messages after modal dismissal

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 :)

How to make charge using stripe in iOS?

I have read all detail about stripe integration in iOS app from here. And run the sample app of stripe by downloading from here.
On running this app, I am getting following error when I am testing on iPhone Simulator.
Following method is calling after token received::
- (void)hasError:(NSError *)error
{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error", #"Error")
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", #"OK")
otherButtonTitles:nil];
[message show];
}
//--Called after sucessfully token received ::
- (void)hasToken:(STPToken *)token
{
NSLog(#"Received token %#", token.tokenId);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://example.com"]];
request.HTTPMethod = #"POST";
NSString *body = [NSString stringWithFormat:#"stripeToken=%#", token.tokenId];
request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
if (error) {
[self hasError:error];
} else {
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"\nStr is :::%#",str);
NSLog(#"\n\nResponse is :::%#",response);
[self.navigationController popViewControllerAnimated:YES];
}
}];
}
Please tell me how to resolve this problem and make a credit card payment on iOS app because in this sample code, there is information about payment.
You are using example.com. Replace it first with your own server address.
For charging a card in Stripe you need a server module. Here is detail explanation of how to charge your card via server module. If you are using parse as backend you can always use cloudcode for this purpose.
Here is raywenderlich's explanation of making payment system with python backend.

Resources