When something goes so horribly wrong that my app can't continue and needs to exit, I want to pop up an alert box to the user, and then close the app when they tap the OK button. Sounds simple enough, right?
But here's the problem: my fatal error handler gets called by a 3rd party library (I don't have their source code). I give them a pointer to my fatal error handler on initialization, and when they encounter a fatal error they simply call that routine and expect it to never return. If it returns, the 3rd party library will assume I've handled the error and it will continue on its way (possibly corrupting data because things are now in an inconsistent state). I could just exit the application at the end of my error handler (which is what they expect), but I want to be able to display a message to the user first to tell them what the problem is.
Unfortunately, if I just do:
-(void)fatalErrorHandler:(NSString *)msg
{
// Log the error and shut down all the things that need
// to be shut down before we exit
// ...
// Show an alert to the user to tell them what went wrong
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened. This is no good.
I need to NOT return from fatalErrorHandler. Ever. But since I'm on the main thread, the UIAlertView won't appear until fatalErrorHandler returns. A catch-22.
Any ideas on how I can show an alert to the user without returning from my fatal error handler?
I don't know if this would work, but what about starting a while loop with a sleep in its body for, let's say, 1 second each cycle? The while would exit when a Bool variable would have been set to YES, maybe from the alertViewDelegate.
From what you wrote "fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened."
I guess what you actually need is to pause everything when the fatalErrorHandler method is called. To achieve this, you can stop all NSTimer, queued methods etc. before displaying alertView.
Alternatively, you can display alertView via a different thread, and then use usleep(long long time) to pause the thread where fatalErrorHandler is in.
Okay, wesley6j's answer gave me an idea. Here's what I came up with:
-(void)fatalErrorHandler:(NSString *)msg
{
// Pop up an alert to tell the user what went wrong. Since this error
// handler could be called from any thread, we have to make sure this happens
// on the main thread because it does UI stuff
[self performSelectorOnMainThread:#selector(showMessage:) withObject:msg waitUntilDone:YES];
// Now, if we're NOT on the main thread, we can just sleep forever and never
// return from here. The error handler will exit the app after the user
// dismisses the alert box.
if (![NSThread isMainThread])
Sleep(0x7fffffff);
else
{
// OTOH, if we ARE on the main thread, we have to get a bit creative.
// We don't ever want to return from here, because this is a fatal error
// handler and returning means the caller can continue on its way as if
// we "handled" the error, which we didn't. But since we're on the main
// thread, we can't sleep or exit because then the user will never see
// the alert box we popped up in showMessage. So we loop forever and
// keep calling the main run loop directly to let it do its processing
// and show the alert. This is what the main run loop does anyway, so
// in effect, we just become the main run loop.
for (;;)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
}
}
-(void)showMessage:(NSString *)msg
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
[alert release];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
This works perfectly and does exactly what I need.
Related
I wanted to understand the following scenario
-(void) foo
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not save file"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//This is called when ok is pressed
}
In the above code a UIAlertView strong pointer is created and a delegate to self is assigned. The reason I called it a strong reference pointer is because it is created in scope and will go out of scope when its reference count goes to 0. I believe the reference count goes to 0 when the method foo ends then why am I still getting a callback at clickedButtonAtIndex ? I was under the assumption that we would not get a callback because the destructor of the alertView instance would have been called as soon as the method foo ended.
I believe the reference count goes to 0
You believe wrong. When you say [alert show], you hand the alert object over to Cocoa, which retains it; otherwise there would be no alert view to appear on the screen! That alert view has a reference (which is actually weak) to you (self). And Cocoa thus is able to hand the very same alert view back to you in the delegate callback; the alert is still alive because Cocoa is still retaining it, and you are still alive because you are still alive, so the reference to self works as the target of the callback.
Also, I can't quite tell whether you grasp that as soon as you say [alert show], the code does not pause - it goes right on, immediately. Thus the first method is over before the alert actually appears on the screen. Again, this works because the alert has been handed over to Cocoa, which retains it and takes care of showing it on the next runloop. None of your code is running while the alert is present on the screen.
A completely parallel situation is
MyViewController* vc = [MyViewController new];
[self.presentViewController:vc animated:YES completion:nil];
The code ends, so why doesn't MyViewController vanish in a puff of smoke? Because presentViewController hands it over to Cocoa, which inserts it into the view controller hierarchy and retains it.
I have a code in a class method that must show a UIAlertView on successful operation (it's a Facebook SDK handler).
But, the alert doesn't show up.
Here's the code:
-(void) postCompletionHandler:(FBRequestConnection *)connection
result:(id)
result error:(NSError*)error
{
if (!error) {
// Link posted successfully to Facebook
NSLog(#"result: %#", result);
UIAlertView *successAlert = [[UIAlertView alloc]
initWithTitle:#"FB Success!"
message:result
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[successAlert show];
}
}
I put a breakpoint inside the block and
[successAlert show];
reaches execution and successfully executes. The debugger also shows, that this code excuted from the main, GUI thread.
I can't even imagine what can be the problem here.
Any ideas?
Update: big thanks for everyone who participated, your help means so much for me.
Now I solved the problem. The problem was, as pointed by #rmaddy, in incorrect parameter result, given to a constructor of an alertView class.
In the future I'll investigate on exception handling techniques in Xcode to prevent such an obvious errors to ruin my code.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Order"
message:#"Order Successfully Discontinued."
delegate:self
cancelButtonTitle:nil
otherButtonTitles: #"Ok",nil];
//[alertView performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
alertView.tag=TAG_DEV;
[alertView show];
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if(alertView.tag==TAG_DEV)
{
if(buttonIndex==0)
{
}
else
NSLog(#"Here");
}
}
This crashes. How can I fix it?
When you crash on a "objc_msgSend()" you are most likely sending a messageto an already-freed object. Or you have a pointer, which is correct, but something have changed the objects contents. Another cause can be the use of a dangling pointer that once pointed to the memory now occupied by your object. Occasionally objc_msgSend crashes because a memory error changed the runtime's own data structures, which then would cause trouble in the receiver object itself.
In this situation you need to check wheter or not, the delegate of the UIAlertview has been released after presenting, so when the alert is dismissed, it is not sending a message to it's delegate, which may be nil. An other option is that the UIViewController that presents the alert view is released after it is presented. Please check if the alert delegate is not released after it is presented.
Followup to Where is NSAlert.h in the iOS SDK?
Is there any way to get NSAlert runModal like behavior from a UIAlertView? Or from a UIActionSheet?
I'm planning on using only in debug builds so I'm not concerned with how it looks or if it uses undocumented functionality.
Edit:
NSAlert is part of the OS X SDK and is similar to MessageBox in Win32. It allows you to synchronously prompt the user for something. Here's an example:
NSAlert * myAlert=[[NSAlert alloc] init];
[myAlert setMessgeText:#"This is my alert"];
[myAlert addButtonWithTitle:#"button 1"];
[myAlert addButtonWithTitle:#"button 2"];
switch ([myAlert runModal]) {
case NSAlertFirstButtonReturn:
//handle first button
break;
case NSAlertSecondButtonReturn:
//handle second button
break;
}
runModal is a synchronous function, it shows the alert and waits for user response. Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped; no messages, no events, nothing.
Internally it is running a limited version of the message loop, but as far as the rest of my application is concerned, the world has stopped
Just do exactly what you described: throw up the alert, then run the event loop till the alert view gets dismissed. This code works:
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"O rlly?" message:nil delegate:nil
cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
NSRunLoop *rl = [NSRunLoop currentRunLoop];
NSDate *d;
while ([alert isVisible]) {
d = [[NSDate alloc] init];
[rl runUntilDate:d];
[d release];
}
[alert release];
You'll have to write your own if you want this behavior. Careful, if you block the main queue for too long, your app will be watchdog'd.
UIAlertView gives you modal behavior, and will end up working the same way your custom class will. You might consider using a block-based wrapper that wraps up UIAlertView and allows you to setup blocks for the button action callbacks.
I have a simple application which was working fine until I added some code which launches a new thread at some point and then tries to show an alert from that thread. Now, the app crashes whenever the code for showing the alert is hit.
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
My question is: is it possible to display UI elements such as alerts from multiple threads on iOS?
You definitely don't want to be displaying an alert (or anything UI-related) from any thread other than the main thread. I'd suggest putting your alert code in a function and call one of the performSelectorOnMainThread calls.
- (void) showAlert
{
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
}
// ... somewhere in the worker thread ...
[self performSelectorOnMainThread:#selector(showAlert) withObject:nil waitUntilDone:NO];
I'm pretty sure that the main thread is the only thread that should be the one that handles UI recognition/drawing things to screen. What I would do if I were in your position would to be either use KVO notifications or implement a protocol that some class subscribes to. Going the protocol route, when you get to the alerting part of your code merely have that thread call its protocol method, the subscribing class will be alerted by having the delegate function triggered and you can easily present whatever you have to in that view via the main thread.
Hope that helps.
Better and simple and just one line approach is to call performSelectorOnMainThread method with alertView.
In your case try this line
[addAlert performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
instead of
[addAlert show];
It will call show method of Alertview on main thread. No need to write any extra method.