Avoiding EXC_BAD_ACCESS when using the delegate pattern

13,078

Solution 1

No, you can't (usefully) "test if an address contains a valid object". Even if you were able to grub around inside the internals of the memory allocation system and determine that your address points to a valid object, that would not necessarily mean that it was the same object that you were previously referring to: the object could have been deallocated and another object created at the same memory address.

Retaining the delegate is the usual way to solve this. Your option (b) breaks object encapsulation, and might have thread-safety issues.

Solution 2

I just ran into this problem and solved it. For ARC, the solution is to use the weak attribute instead of assign.

The crash come because the delegate

  1. Has an assign attribute, AND
  2. Has been deallocated.

The solution is to use the weak attribute, because when the object deallocates, the pointer WILL be set the nil . So when your code calls respondsToSelector on a nil, Objective C will ignore the call, and not crash.

In your code, when you attempt to call the respondsToSelector method on delegate, you get a EXC_BAD_ACCESS. This is because objects that use the assign property will not be set to nil when they are deallocated. (Hence why doing a !self.delegate before the respondsToSelector does not prevent the responseToSelector from being called on a deallocated object, and still crashes your code)

As already mentioned, using a strong or assign attribute on a delegate (as many have mentioned) in ARC will result in a retain cycle. So don't do it, you don't need to.

Solution 3

I also have problems with delegate weak reference, and currently I have only one solution for this problem: use strong reference for delegate, and manually set self.delegate = nil; after my code is complete. This solution works for me in async images loading, where you have some life cycle with visible ending.

Solution 4

I would just write

SEL slc = @selector(theSlc);
if ([delegate respondsToSelector:slc]) {
    [delegate performSelector:slc];
}

If the object is valid the method will be called, otherwise not. You do not have to check for

self.delegate != nil

Solution 5

I came across this question because my "downloader" object was giving me EXC_BAD_ACCESS. My solution was the cancel the downloader object right before I released it. Assuming your using NSURLConnection in your downloader object, call the cancel method on it.

Its important to note that if NSURLConnection is not currently downloading anything then calling cancel will result in a crash. You will need some logic to check if a download is in progress.

Share:
13,078
Kenny Winker
Author by

Kenny Winker

I make iPhone, iPad, Mac, and Web apps. Usually ineptly.

Updated on July 12, 2022

Comments

  • Kenny Winker
    Kenny Winker almost 2 years

    A have a view controller, and it creates a "downloader" object, which has a reference to the view controller (as a delegate). The downloader calls back the view controller if it successfully downloads the item. This works fine as long as you stay on the view, but if you navigate away before the download is complete I get EXC_BAD_ACCESS. I understand why this is happening, but is there any way to check if an object is still allocated? I tried to test using delegate != nil, and [delegate respondsToSelector:], but it chokes.

    if (!self.delegate || ![self.delegate respondsToSelector:@selector(downloadComplete:)]) {
      // delegate is gone, go away quietly
            [self autorelease];
            return;
        }
    else {
      // delegate is still around
      [self.delegate downloadComplete:result];
    }
    

    I know I could,

    a) have the downloader objects retain the view controller

    b) keep an array of downloaders in the view controller, and set their delegate values to nil when I deallocate the view controller.

    But I wonder if there is an easier way, where I just test if the delegate address contains a valid object?

  • Kenny Winker
    Kenny Winker about 14 years
    most of apple's implementations I can see don't retain the delegate... e.g. @property(nonatomic,assign) id <UITableViewDelegate> delegate; am I mis-applying the design pattern?
  • David Gelhar
    David Gelhar about 14 years
    That may be true for UI objects that require all interaction to happen on the main thread, but I think you'll find that implementations that are doing async operations in background threads (such as NSURLConnection) will retain their delegate.
  • Admin
    Admin about 14 years
    A delegate is NEVER retained! Only targets are retained.
  • Kenny Winker
    Kenny Winker about 14 years
    Could you please explain more or provide a reference, @eaigner. I googled a bit and can find no definition of a target.
  • Kenny Winker
    Kenny Winker about 14 years
    You're probably right that my !self.delegate is redundant. But, I do know that [delegate respondsToSelector:] doesn't protect you from EXC_BAD_ACCESS. Try running this: NSObject *obj = [[NSObject alloc] init]; [obj release]; for (int i = 0; i<2; i++) { if ([obj respondsToSelector:@selector(retainCount)]) NSLog(@"retain count: %i",[obj retainCount]); }
  • Admin
    Admin about 14 years
    A delegate is an object that receives events, but is not retained by the sender. A target - for instance a NSButton/Control has a target and an action - is retained by the sender.
  • David Gelhar
    David Gelhar about 14 years
    @eaigner What's your reference for the claim that a delegate is never retained? For example, the NSURLConnection method -initWithRequest:delegate: does retain the delegate (and is documented to do so).
  • Hexark
    Hexark almost 10 years
    besides using weak, is there other things required? Like eg, 'delegate = nil' in the main class or setting 'id _strong delegate'? I assume im haivng the same issue stackoverflow.com/questions/24466244/…
  • Drew H
    Drew H almost 10 years
    You should not need to do anything else. I read your question, and noticed that you provided a response indicating that using weak instead of assign fixed the crash. Are you still experiencing problems?
  • Manoj
    Manoj over 9 years
    Thank you.. your answer saved my day @DrewH
  • sqwerl
    sqwerl over 9 years
    It's also not true that targets are retained all the time...UIGestureRecognizer addTarget:action: does not retain the target.
  • ViruMax
    ViruMax over 9 years
    Great explanation, however I am facing problem with weak delegate also.
  • Antony Raphel
    Antony Raphel over 7 years
    Thank you, it's working fine! if (self.delegate && [(NSObject*)self.delegate respondsToSelector:@selector(customMethod:clickedButton:)]) { [delegate customMethod:self clickedButton:[sender tag]]; } with weak attribute delegete