flohei
flohei

Reputation: 5308

UIAlertView crashes when button is tapped

I'm having some trouble with an ordinary UIAlertView.

Scenario: My app is capable of importing it's own data type (for backup/restore). Upon -application:openURL:sourceApplication:annotation: I create my importer class and start the import action itself. Before the actual import starts, the user should be asked whether or not he wants to delete/overwrite his old database.

The UIAlertView shows up nicely. When I hit one of the two buttons (Yes and No) the app crashes without any information on the console. I'm raising the alert on the main thread. It gets created like this:

UIAlertView *dataAvailableAlert = [[UIAlertView alloc] initWithTitle:alertTitle
                                                             message:alertMessage
                                                            delegate:self
                                                   cancelButtonTitle:noButtonTitle
                                                   otherButtonTitles:yesButtonTitle, nil];
[dataAvailableAlert show];

My class adopts the UIAlertViewDelegate and implements -alertView:clickedButtonAtIndex:. The latter does not get called at all. Update: I'm using ARC. Backtrace:

* thread #1: tid = 0x1c03, 0x01bc809f libobjc.A.dylib`objc_msgSend + 19, stop reason = EXC_BAD_ACCESS (code=1, address=0xfd940000)
    frame #0: 0x01bc809f libobjc.A.dylib`objc_msgSend + 19
    frame #1: 0x00eee0bc UIKit`-[UIAlertView(Private) _buttonClicked:] + 294
    frame #2: 0x01bca705 libobjc.A.dylib`-[NSObject performSelector:withObject:withObject:] + 77
    frame #3: 0x00afe2c0 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 96
    frame #4: 0x00afe258 UIKit`-[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
    frame #5: 0x00bbf021 UIKit`-[UIControl sendAction:to:forEvent:] + 66
    frame #6: 0x00bbf57f UIKit`-[UIControl(Internal) _sendActionsForEvents:withEvent:] + 578
    frame #7: 0x00bbe6e8 UIKit`-[UIControl touchesEnded:withEvent:] + 546
    frame #8: 0x00b2dcef UIKit`-[UIWindow _sendTouchesForEvent:] + 846
    frame #9: 0x00b2df02 UIKit`-[UIWindow sendEvent:] + 273
    frame #10: 0x00b0bd4a UIKit`-[UIApplication sendEvent:] + 436
    frame #11: 0x00afd698 UIKit`_UIApplicationHandleEvent + 9874
    frame #12: 0x02779df9 GraphicsServices`_PurpleEventCallback + 339
    frame #13: 0x02779ad0 GraphicsServices`PurpleEventCallback + 46
    frame #14: 0x024b5bf5 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
+ 53
    frame #15: 0x024b5962 CoreFoundation`__CFRunLoopDoSource1 + 146
    frame #16: 0x024e6bb6 CoreFoundation`__CFRunLoopRun + 2118
    frame #17: 0x024e5f44 CoreFoundation`CFRunLoopRunSpecific + 276
    frame #18: 0x024e5e1b CoreFoundation`CFRunLoopRunInMode + 123
    frame #19: 0x027787e3 GraphicsServices`GSEventRunModal + 88
    frame #20: 0x02778668 GraphicsServices`GSEventRun + 104
    frame #21: 0x00afaffc UIKit`UIApplicationMain + 1211
    frame #22: 0x00002e8d Foo`main(argc=1, argv=0xbffff61c) + 141 at main.m:16
    frame #23: 0x00002db5 Foo`start + 53

Any idea whats wrong here?

Thanks
–f

Update: As requested the entire class.

@implementation NEADatabaseImporter

- (id)initWithDictionary:(NSDictionary *)importDictionary {
    self = [super init];
    if (self) {
        _importDictionary = importDictionary;
    }

    return self;
}

- (void)startImport {
    // check if the current database already has some data in it
    BOOL dataCreated = ([[NEADataAccessor groups] count] > 1);

    // if so ask the user if he wants to delete the old data
    if (dataCreated) {
        // prepare variables for better reading
        NSString *alertTitle = NSLocalizedString(@"REPLACE_DATABASE_TITLE", @"The title for the alert asking the user to let the app replace his database upon import.");
        NSString *alertMessage = NSLocalizedString(@"REPLACE_DATABASE_MESSAGE", @"The message the alert displays when asking the user for permission to replace the database upon import.");
        NSString *yesButtonTitle = NSLocalizedString(@"YES", @"Yes");
        NSString *noButtonTitle = NSLocalizedString(@"NO", @"No");

        UIAlertView *dataAvailableAlert = [[UIAlertView alloc] initWithTitle:alertTitle
                                                                     message:alertMessage
                                                                    delegate:self
                                                           cancelButtonTitle:noButtonTitle
                                                           otherButtonTitles:yesButtonTitle, nil];
        [dataAvailableAlert show];
    } else {
        // if there was no data yet we can directly invoke the import
        [self removeDatabase];
        [self readDatabaseImportDictionary];
    }
}

- (void)removeDatabase {
    NEAAppDelegate *delegate = (NEAAppDelegate *)[[UIApplication sharedApplication] delegate];
    [[delegate databaseManager] clearStores];
}

- (void)readDatabaseImportDictionary {
    if (![self importDictionary]) return;

    // read it here
    NEAAppDelegate *delegate = (NEAAppDelegate *)[[UIApplication sharedApplication] delegate];
    [[delegate currentProfile] readDataDictionary:[self importDictionary]];
}

#pragma mark - UIAlertViewDelegate

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    NSInteger yesIndex = 1;

    if (buttonIndex == yesIndex) {
        // go ahead and replace the database
        [self removeDatabase];
        [self readDatabaseImportDictionary];
    } else {
        // cancel the import
        NSLog(@"import of dictionary canceled: %@", [self importDictionary]);
    }
}

@end

Update: shannoga is right. It looks like my NEADatabaseImporter is just getting released too early. When I make it a strong property of the calling class, it works just fine. But making everything a strong property can't be the solution, can it? Whats the correct way to store a local variable strongly using ARC?

Upvotes: 1

Views: 827

Answers (1)

shannoga
shannoga

Reputation: 19869

Well its a guess, but I had the same problem few months ago.

My problem was that the delegate of the alert view was released after presenting so when the alert was dismissed it sends a message to it's delegate but it does not exist anymore.

An other option is that the viewController that presents the alert view is released after it is presented.

So basically check if the who is the alert delegate is not released after is presented.

Hope it will help

Upvotes: 9

Related Questions