This is a very very... very odd bug. It's hard to describe the exact project I have, but I will try and create a simpler representation of the classes I have. It goes like this:
Assume I have a navigation controller as my top view controller. Inside it, at one moment in time I have a UIViewController, let's say a ContactsScreenController. The view for this contains multiple UITableView that each is controlled by a separate object of type MyTableController (delegate&datasource). I do this by keeping an array of controllers
// This is the interface for my screen controller. An object of this type goes in a top-
// level navigation controller
// MainScreenController.h
@interface ContactsScreenController : UIViewController
NSMutableArray* tableControllers;
@end
// MainScreenController.m
- (UITableViewCell*)cellForRowAtIndexPath..something..
{
// Here what I do is create a new controller if needed, and add it to tableControllers
// Memory allocations & releases are good because I checked with instruments
}
#define SAFE_DEL(x) { if (x != nil) { [x release]; x = nil; } }
- (void)dealloc
{
SAFE_DEL(tableControllers);
[super dealloc];
}
Now, MyTableController is a more complicated object as it handles fetching data from a web service, but basically what I do is I want to make sure that when the object is deleted, I cancel any pending data requests, like this:
// MyTableController.m
- (void)dealloc
{
[globalDataProvider cancelRequestsForController:self];
// release other objects i might have
[super dealloc];
}
OK, so this is my objects setup. The crash occurs when I am deleting the object tableControllers. It decrements the retainCount for my MyTableController objects and it reaches 0 (checked using Instruments). But for some UNKNOWN reason, I get calls for cancelRequestsForController, AFTER the retain count has been zero. Obviously, I get a EXC_BAD_ACCESS.
Before you start thinking it's a problem with my retain/release pairs, the application runs perfectly if I am releasing the main screen controller while the inner tables are static. As soon as the are scrolling and I hit the Back button in the navigation controller I experience the bug.
I've checked using instruments the entire history of retain count changes for my inner controllers and it is good (no unusual stuff). When the bug occurs, my last entry in the history is from QuartzCore run_animation_callbacks with a retain count of -1.
Any ideas? :)
PS: As a quick solution to get the project going, I've moved the cancelRequestsForController in a separate method and I'm manually calling it for each object in tableControllers before the release. This way I am sure that there will be no calls after the release, no matter the state of the tab开发者_JS百科leview.
- (void)dealloc
{
for (TableController* c in tableControllers)
[c cancelRequests];
SAFE_DEL(tableControllers);
[super dealloc];
}
But I don't like this solution for several reasons.
Thanks to everybody for the answers/comments.
The problem was generated by a call to [super dealloc]
in an object, before I got the chance to release my objects. This caused a lot of crazy stuff to happen. I moved the [super dealloc]
at the end of my dealloc method (after my releases) and it works fine now.
The SAFE_DEL
macro is unnecessary and makes the code less readable. Simply do:
[someObject release], someObject = nil;
It won't matter if someObject
is already nil and it makes the code more directly readable.
As soon as the are scrolling and I hit the Back button in the navigation controller I experience the bug.
Any time you have non-memory management logic, you have fragility. Namely, when dealloc
is being executed, it is quite likely because there is an entire sub-graph of objects in your application that are being deallocated. Since deallocation order is largely non-deterministic, you can't safely trigger any complex behavior in dealloc
without risk that you are going to message an already dealloc
ated object.
The best solution would be to get that cancellation mechanism out of dealloc
!
Two things to check: 1. does your globalDataProvider (or any other class for that matter) have a reference to your controller (i.e. to call it back with data?) Your retain counts could be zero but for the wrong reason. How are you allocating the array, as well as each controller in the array? Do you have @properties? 2. In your executables properties Arguements screen, set NSZombieEnabled=YES That will tell you exactly which object is being called when it has a zero retain count. HTH, -Mike
精彩评论