3
votes

SOLVED (I think): Set ASIHTTPRequest delegate/NSOperationQueue delegate to nil after the vc is popped (in the dealloc).

Before I start, this question is similar but not the same.

My app is very near release, however this is the only persistent bug which has existed throughout development. It is also the only bug which has occured when demoing the app to somebody, and because this bug results in termination of the app, it's highly embarassing. If someone can find a fix for this (for good), I will award a decent-sized bounty.


Start of question:

Basically, this bug occurs when the ASIHTTPRequest tries to call the requestFinished selector, onto an object which is a zombie.

From looking around, it appears that what ASIHTTPRequest, when faced with sending a selector to a zombie, will send it to a similar object that isn't a zombie. I've heard it's quite common to have it send the selector to the CALayer of the controller it was meant to send to, and also with me it's sent to my UINavigationButton!? - Anyhow, the effect of this is that it throws an exception.

The problem occurs inside the ASIHTTPRequest class when it tries to call the 'requestFinished' selector:

[queue performSelector:@selector(requestFinished:) withObject:self];

That line of code is the one it fails on - what puzzles me is why? On the question I linked to, the OP said that they fixed it by calling [self retain] from the view controller. Surely that isn't right?

Structure

The way my ASIHTTPRequest's work, is they are added to a downloadQueue, (NSOperationQueue), which resides within my app delegate. The structure/style of my app is mainly based on this sample code, which does a very similar thing but with the flickr RSS feed.

The section of my app that uses ASIHTTPRequest, is a webcam viewer. It has a navigation controller with a table view in, that displays three webcams. These images are JPEGs and updated every 30 seconds to a static file (on a remote server). The cells within this table view are custom, and each load their own ASIHTTPRequest. The cells download their respective images, and display them.

Here is the method I created which is called on each of my cells:

- (void)loadURL:(NSURL *)url {
    ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:url];
    [request setDelegate:self];
    [request setDidFinishSelector:@selector(requestDone:)];
    [request setDidFailSelector:@selector(requestWentWrong:)];
    NSOperationQueue *queue = [[MyAppDelegate sharedAppDelegate] downloadQueue];
    [queue addOperation:request];
    [spinner startAnimating];
    [self addSubview:spinner];
    [request release];  
}

When one of these cells is selected, it takes the user to a detail view, where another instance of ASIHTTPRequest is loaded, which downloads the image for the selected webcam.

How the bug is triggered

Under normal "calm" usage of my app, it behaves fine. The problem occurs when rapid pushing and popping occurs between these views (between the root and detail webcam views). It most often occurs if you enter a detail view, the progress bar begins moving, and you pop to the root again. This leads me to believe that because the controller is not active, when the ASIHTTPRequest finishes, and it tries to call the controller, it fails.

Crash log

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00000480
Crashed Thread:  0

Thread 0 Crashed:
0   libobjc.A.dylib                 0x30183f24 objc_msgSend + 24
1   iCompton                        0x0003c4d4 0x1000 + 242900
2   CoreFoundation                  0x35ea3f72 -[NSObject(NSObject) performSelector:withObject:] + 18
3   iCompton                        0x00029082 0x1000 + 163970
4   CoreFoundation                  0x35ea3f72 -[NSObject(NSObject) performSelector:withObject:] + 18
5   Foundation                      0x33fd3e66 __NSThreadPerformPerform + 266
6   CoreFoundation                  0x35ebc8ca __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 8
7   CoreFoundation                  0x35e8cec6 __CFRunLoopDoSources0 + 378
8   CoreFoundation                  0x35e8c6f2 __CFRunLoopRun + 258
9   CoreFoundation                  0x35e8c504 CFRunLoopRunSpecific + 220
10  CoreFoundation                  0x35e8c412 CFRunLoopRunInMode + 54
11  GraphicsServices                0x35261d1c GSEventRunModal + 188
12  UIKit                           0x33865574 -[UIApplication _run] + 580
13  UIKit                           0x33862550 UIApplicationMain + 964
14  iCompton                        0x00002b92 0x1000 + 7058
15  iCompton                        0x00002b44 0x1000 + 6980

  • Thanks if you read this far!
  • Thanks even more if you answer
  • Bounty if you solve my problem :)
3

3 Answers

6
votes

It most often occurs if you enter a detail view, the progress bar begins moving, and you pop to the root again. This leads me to believe that because the controller is not active, when the ASIHTTPRequest finishes, and it tries to call the controller, it fails.

I think you are right on track on this.

What you should try to do is cancelling the ASIHTTPRequest when you pop to the root controller (or to whatever other controller). Before you cancel the request, you should set its delegate to nil; or you can use clearDelegatesAndCancel.

Possibly this will require you to keep a reference to the request in your view controller, but that should be no problem.

An interesting S.O. question/answer about detecting UIViewController being popped is here.

0
votes

You should not be using NSZombieEnabled in live apps, the memory is never released. You need to turn it off when not in development. The rapid pushing and pulling is probably exhausting the memory, triggering memory warnings, and causing things to be released and dealloc'd. We won't know for sure though until you post the crash log.

0
votes

Here's how I solved this problem: set the delegate of any outstanding ASIHTTPRequests to nil in your viewController's dealloc method.

This prevented the outstanding ASIHTTPRequests from sending messages to the didFinishSelectors, didFailSelectors etc on the viewController after the viewController was popped off the navigation stack and dealloc'd.

Keep in mind: the ASIHTTPRequest method cancelAllOperations possibly calls the didFailSelector asynchronously. In this case the selector might be called on the viewController which no longer exists.