How to prevent crash on Cancel of MFMailComposeViewController?

Somewhere:

if([MFMailComposeViewController canSendMail])
{
    MFMailComposeViewController *email_vc = [[MFMailComposeViewController alloc] init];
    email_vc.mailComposeDelegate = self;

    [email_vc setSubject:subject];
    [email_vc setMessageBody:message isHTML:FALSE];
    [email_vc setToRecipients:recipients];

    [self presentModalViewController:email_vc animated:FALSE];
    [[UIApplication sharedApplication] setStatusBarHidden:TRUE];
    [email_vc release];
}
else
...

Somewhere else:

- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
    switch (result) 
    {
        case MFMailComposeResultCancelled:
            NSLog(@"Cancelled");
            break;

        case MFMailComposeResultSaved:
            NSLog(@"Saved");
            break;

        case MFMailComposeResultSent:
            NSLog(@"Sent");
            break;

        case MFMailComposeResultFailed:
            NSLog(@"Compose result failed");
            break;

        default:
            NSLog(@"Default: Cancelled");
            break;
    }

    // This ugly thing is required because dismissModalViewControllerAnimated causes a crash
    // if called right away when "Cancel" is touched.

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC), dispatch_get_current_queue(), ^
    {
        [self dismissModalViewControllerAnimated:FALSE];
    }); 

}

That ugly "dispatch_after" block is the only way I can get this to work without a crash.

The context is that touching anything other than "Send" on the email compose view controller will cause a crash. Is there a way to deal with this without having to resort to this ugly band-aid? My theory on the band-aid is that an intermediate view is being presented when you touch "Cancel" to confirm that the user really wants to cancel. I am wondering if [self dismissModalViewControllerAnimated:FALSE]; is trying to dismiss a view out of sequence or something to that effect. By inserting a small delay I am theorizing that the mail compose view has time to cleanup before it is asked to go away.

I've seen a delay used in another question. The author did not go into any details though:

Crash On MFMailComposeViewController For iPad

EDIT 1: Adding crash log

Incident Identifier: ****************
CrashReporter Key:   *****************
Hardware Model:      iPhone4,1
Process:         ************* [9038]
Path:            /var/mobile/Applications/*********************
Identifier:      ***********************
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]

Date/Time:       2012-07-20 11:25:53.704 -0700
OS Version:      iPhone OS 5.0.1 (9A405)
Report Version:  104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0xa003853a
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x316b9fbc 0x316b6000 + 16316
1   UIKit                           0x350caa9e 0x34f8e000 + 1297054
2   UIKit                           0x34fa6814 0x34f8e000 + 100372
3   UIKit                           0x34fabfb2 0x34f8e000 + 122802
4   QuartzCore                      0x33354ba0 0x33329000 + 179104
5   libdispatch.dylib               0x37896f74 0x37894000 + 12148
6   CoreFoundation                  0x37bac2d6 0x37b20000 + 574166
7   CoreFoundation                  0x37b2f4d6 0x37b20000 + 62678
8   CoreFoundation                  0x37b2f39e 0x37b20000 + 62366
9   GraphicsServices                0x376adfc6 0x376aa000 + 16326
10  UIKit                           0x34fbf73c 0x34f8e000 + 202556
11  *****************               0x00002346 main (main.m:14)
12  *****************               0x00002304 start + 32

EDIT 2: After much head scratching it appears that this is a genuine Apple bug.

I downloaded and ran the MailComposer sample project:

http://developer.apple.com/library/ios/#samplecode/MailComposer/Introduction/Intro.html

It works fine.

Then I edited the code to remove the animation while presenting and dismissing the mail composition controller.

[self presentModalViewController:picker animated:FALSE];

and

[self dismissModalViewControllerAnimated:FALSE];

Sure-enough, it crashed when "Cancel" was used to dismiss the email composition UI.

Running zombie brought this out:

-[MFMailComposeController actionSheet:didDismissWithButtonIndex:]: message sent to deallocated instance 0x7479ef0

I guess the action sheet gets the dismiss message instead of the mail compose view controller.

If someone could confirm behavior I'll report the bug.

EDIT 3: Bug reported.

The answer I accepted has a good explanation of the potential mechanism that is causing this issue. Also, during the back and forth in the answer comments two additional work-arounds were identified. All band-aids but now there are a few choices.

I haven't checked yet, but I suspect that ShareKit is subject to this bug as well (if the presentation of the mail compose view controller is not animated).


I guess the action sheet gets the dismiss message instead of the mail compose view controller.

Not quite.

The sequence of events probably happens like this:

  • Action sheet calls -actionSheet:clickedButtonAtIndex: on its delegate (the MFMCVC).
  • MFMailComposeViewController calls -mailComposeController:didFinishWithResult:error: on its delegate (your VC)
  • Your VC calls [self dismissModalViewControllerAnimated:NO]
  • This causes the MFMCVC to be released. Since the dismiss isn't animated, there is no longer anything referring to the MFMCVC. It gets dealloced!
  • Action sheet calls -actionSheet:didDismissWithButtonIndex: on its delegate
  • But its delegate has been dealloced!
  • So it crashes!
  • The fix would be for Apple to do actionSheet.delegate = nil in -dealloc .

    A potential workaround

    [[self.modalViewController retain] autorelease]
    [self dismissModalViewControllerAnimated:NO]
    

    This is a bit trickier to do if you are using ARC.


    this works for me:

    - (void) mailComposeController: (MFMailComposeViewController *) controller
           didFinishWithResult: (MFMailComposeResult) result
                         error: (NSError *) error {
    
    if(result == MFMailComposeResultSent){
        [self dismissViewControllerAnimated:YES completion:NULL];
    } else if (result == MFMailComposeResultCancelled) {
        [self dismissViewControllerAnimated:YES completion:NULL];
    }
    

    }

    链接地址: http://www.djcxy.com/p/94540.html

    上一篇: 在MFMailComposeViewController中抑制“保存草稿”按钮

    下一篇: 如何防止取消MFMailComposeViewController的崩溃?