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.
Related
The object is deallocated in ARC mode and causes crash. My code below;
BlockAlertView* alert = [BlockAlertView alertWithTitle:title message:message];
[alert setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
[alert addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
}];
[alert show];
It appears the correct alert view(which is custom UIView) but when I click one of the button it crashes.
The crash log;
2015-04-07 22:28:17.065 Test[3213:781570] <BlockAlertView: 0x16bb4160>
2015-04-07 22:33:01.746 Test[3213:781570] *** -[BlockAlertView performSelector:withObject:withObject:]: message sent to deallocated instance 0x16bb4160
Here is the source code of BlockAlertView;
BlockAlertView on Github
For now I can't estimate any of the clues for this and makes me old.
Any input will be much appreciated!
Presumably, that code worked correctly before it was converted to ARC.
To fix it, you'll need to create a strong reference to self in the -show method, and release this reference in -dismissWithClickedButtonIndex:animated: (where you see [self autorelease] commented out).
You can do this with a simple instance variable:
id _selfReference;
Assign self to _selfReference in -show:
- (void)show
{
_selfReference = self;
...
and then set _selfReference to nil in -dismissWithClickedButtonIndex:animated: in the two places where you see [self autorelease] commented out.
Assign your alert object to live somewhere beyond your current function. One simple possibility is to just make it an instance variable. If that's not practical, create an instance NSMutableArray *retainedItems; which you allocate/init, and stuff this into.
Looks like a design flaw in that project. The class is poorly named BlockAlertView as it's not actually a subclass of UIView. If it were a view and it was added to the view hierarchy then the view hierarchy would ensure it stayed alive whilst being viewed. As it is the view is kept alive but the object that created the view BlockAlertView is not held onto by anything and by the time the actions are called BlockAlertView is long gone.
This will require you to keep a strong ivar around to reference this "controller" object and it would be sensible to nil out that ivar in the completion blocks.
BlockAlertView *alertController = [BlockAlertView alertWithTitle:title message:message]; {
[alertController setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
__weak __typeof(self) weakSelf = self;
[alertController addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
weakSelf.alertController = nil;
}];
[alertController show];
}
self.alertController = alertController;
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.
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.
This is my code
audioViewController *voiceRecorder = [audioViewController sharedManager];
[voiceRecorder stopRecording];
NSString *msg = [NSString stringWithFormat:#"Want to logout?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Info"
message:msg
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
alert.tag = 100;
[alert show];
I am calling sharedManager in one of my view controller. The problem is, my alertview runs before sharedManager method executes, if you check my code, i have called "StopReording" method, but when i run the code, it works after showing alert. Anyone has idea, how do I show alert only after the method returns something.?
You seem to be confusing yourself about method run order and alert presentation order. The methods run in the order specified by your code, they must. What you see on screen is 2 alerts, one (stop) presented first, the the other (logout) presented immediately after.
Generally, you shouldn't show 2 alerts at the same time. Certainly not if they relate to different things.
Present your first alert, then wait for the answer to be received (using the delegate methods). Once you have the users answer, then decide what to do next and present the second alert or continue with some other operation.
I'm wrapping an UIAlertView inside a regular NSObject to allow completion handler blocks instead of the delegate pattern.
The problem is that I allocate a local instance of my object, that internally creates an UIAlertView and assigns its delegate to the object itself. When the alert is shown and the user taps a button, the apps crashes with an EXC_BAD_ACCESS because ARC has released my object and the delegate of the alert is that object.
How could I handle this situation? I saw that a solution is to qualify the local variable with __block and use the object itself inside the completion block, but that doesn't work.
By the way, if I subclass 'UIalertView' instead of wrapping it, it works, but documentation says that alert subclassing is not recommended, so I prefer to resolve this issue.
You can associate your object with the alert view like so:
#import <objc/runtime.h>
...
- (void)showAlertView
{
UIAlertView *alertView = [[UIAlertView alloc] initWithWhatever:...];
objc_setAssociatedObject(alertView, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[alertView show];
}
That will retain your object, and then release it again when the alertView is dealloced. Your object mustn't retain the alertView, or you'll have a retain cycle.