Ok, this problem has me so completely stumped I'm potentially about to lose my best clients over it. I can't account for the behavior in any way I know how, so I'm gonna see if anybody else can spot the problem here or at least give me a suggestion.
Basically, the application's structure is simple. There is a generic Table View Controller object that derives the menu structure of the application out of NSArray objects supplied by a plist file. The leaves of this tree structure are web views, fed a URL to local files, some URL's, some PNG's. Some menu entries terminate without leading to a file.
Now, here's the problem. We want rotation support for certain end files. I've implemented the standard rotation methods and it's working out pretty fantastically. The problem is that there are crashes every so often with 'Unrecognized Selector' exceptions.
I've gotten this error on a 'UISearchDisplayController' and a 'UITableViewCell'. The table views are working properly whenever they're displayed, but when the web view is displayed, and then rotated, the table views invalidate their rows and begin scanning the delegate and data source for content. Unfortunately, whatever they're inspecting seems to be a more or less random object. Here's the traceback from a recent crash:
The clincher here is that the code doesn't crash consistently. I've had it occur the first time I rotated a view, and I've had it occur the second or third time, on a different view. Since rotation is supported exclusively on web views, I honestly have no idea why the table views are getting notifications of the rotation event at all!
And there's one final piece in this puzzle that seems to indicate the issue, at least in some manner or another. While trying to generate a second stack trace to show that the same problem can occur to multiple objects, sometimes, I simply get a good old fashioned 'EXC_BAD_ACCESS', without any stack trace at all even though the debugger's following execution.
Wow, that's so bizarre. There were two excess retains when I ran the program through analysis, and fixing those up took the crash away.
It's always been my understanding that an extra retain would just amount to the same thing as calling 'alloc()' but never calling 'free()'--that the memory would remain allocated, but it wouldn't interfere with the program.
How is it that an object, allocated but allowed to fall out of memory, could cause this type of bug?
You've misunderstood something. Excess retains result in leaks. Leaks don't cause crashes. I can't think of a reason for removing a retain to fix a crash. Quite the opposite.
Wow, that's so bizarre. There were two excess retains when I ran the program through analysis, and fixing those up took the crash away.
It's always been my understanding that an extra retain would just amount to the same thing as calling 'alloc()' but never calling 'free()'--that the memory would remain allocated, but it wouldn't interfere with the program.
How is it that an object, allocated but allowed to fall out of memory, could cause this type of bug?
I'd be very cautious before declaring the problem resolved. Like Brian said, the type of error you saw is indicative of a dangling pointer; the object originally being pointed to was deallocated prematurely, and the memory was reused for some other object, which has no idea how to respond to the message being passed to it. That's why you get random types of objects throwing the Unrecognized Selector exception.
A big problem with these types of bugs is that it's very possible for your app to appear to work normally, even if the problem hasn't been fixed. Remember, when an object is deallocated, it isn't necessarily removed from memory right away; if the system doesn't need to reclaim that memory to use for something else, your deallocated object will stick around and continue to respond to messages passed to it as if everything was normal. Sometimes, the problem never manifests itself until you try running the app on a different platform (e.g., it works in the simulator, but not on the device, or it works on an iPhone but crashes on an iPod touch).
The point is that it's very possible that you haven't actually fixed the bug, but you've just stopped seeing it because an unrelated change to your app coincidentally caused it to stop placing a new object in that specific location in memory. You need to go back and keep track of the retains and releases being sent to the destroyed object and figure out if and when it's being under-retained or over-released.
2010-08-22 02:32:35.694 PsychDx[232:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UISearchDisplayController numberOfSectionsInTableView:]: unrecognized selector sent to instance 0x5fdbbc0'
From the console, po 0x5fdbbc0 will show which object is being called.
Something still seems fishy. Removing extra retains should not have fixed your crash.
Are you releasing any auto-released objects?
What is your view hierarchy? From your description, is it a) a table view that is used to pick which web resource to load into a UIWebView or b) a table view with web views inside the actual table? Do you have a root view controller that manages both the table view and leaf web views?
Most of this code isn't mine, I'm doing feature additions. The problem ONLY occurs during rotate, and it hasn't happened anywhere else. Running the analyzer over the application revealed two leaked objects, but no double-releases.
For the reasons mentioned above, I was telling my client straight away that there was no way those leaks were causing the bug. But after fixing them, the bug's symptoms did go away. I'm sort of ambivalent about working really hard to fix this bug, which I know makes me a bad programmer, but I'm only getting paid for feature additions.
I'm not averse to poking around it a bit, and at least warning the client that the bug may have persisted if it doesn't show.
Basically, is the suggestion that I ought to start walking carefully through the release counts to check the situation?
The one thing that confuses me is that I've had autorelease problems, and I've seen the stack trace before. The thing is that an arbitrary object will be subbed for whatever is supposed to be receiving the method call, and it seems the only way that could be explained is if there's an array of objects and calls are occurring via an offset into that stack, and some source object is getting its 'index' wrong.
But that doesn't seem like a reasonable approach for any modern OS, mobile or otherwise, hence my confusion.
The one thing that confuses me is that I've had autorelease problems, and I've seen the stack trace before. The thing is that an arbitrary object will be subbed for whatever is supposed to be receiving the method call, and it seems the only way that could be explained is if there's an array of objects and calls are occurring via an offset into that stack, and some source object is getting its 'index' wrong.
But that doesn't seem like a reasonable approach for any modern OS, mobile or otherwise, hence my confusion.
Unless I'm misunderstanding what you're describing, this sounds exactly like the dangling pointer issue I discussed above. Or are you referring to something different?
Unless I'm misunderstanding what you're describing, this sounds exactly like the dangling pointer issue I discussed above. Or are you referring to something different?
Ok, I guess I'm probably the one that doesn't understand. How in the hell is it possible for an allocated object that has been leaked to cause an object that has not been leaked to confuse its references to other objects that have not been leaked?
What's happening here is some object completely unrelated to any of the crashes is leaked. Then, these other objects that are just doing their thing, perfect reference containment and everything, are getting majorly confused about what's the Table View and what's a Table View Cell and what's a Search Results Display Controller.
In other words, this leaked object is making the entire program, start to finish, confused about which objects are which. I can't see any mechanism that'd allow this to be the case.
Ok, I guess I'm probably the one that doesn't understand. How in the hell is it possible for an allocated object that has been leaked to cause an object that has not been leaked to confuse its references to other objects that have not been leaked?
What's happening here is some object completely unrelated to any of the crashes is leaked. Then, these other objects that are just doing their thing, perfect reference containment and everything, are getting majorly confused about what's the Table View and what's a Table View Cell and what's a Search Results Display Controller.
In other words, this leaked object is making the entire program, start to finish, confused about which objects are which. I can't see any mechanism that'd allow this to be the case.
What I'm suggesting is that there were probably two completely unrelated bugs in the original app:
The object that you were trying to call the method on was deallocated prematurely, leaving a dangling pointer. As discussed above, the behavior of dangling pointers is basically a crapshoot; they may continue to work normally for a while, if the dead object hasn't actually been overwritten in memory yet.
There were other completely unrelated memory leaks.
If this is correct, then you fixed the second bug, but you didn't fix the first. The bugs were unrelated, but because fixing the memory leak might have shifted the locations where the app is writing things into memory, it's possible that the memory for the deallocated object is now not being overwritten for a much longer time. This makes it seem that the app is now functioning normally, when in reality things could blow up at any time, if app happens to reuse the dead object's memory for something else and then sends the message to the dangling pointer again.
This is all hypothetical, of course, but a "shape-shifting" object that seems to switch types when you try to call a method is a pretty strong symptom of a dangling pointer. As you said above, it's probably worth walking through and checking retain/release counts of the affected object.
Ok, I guess I'm probably the one that doesn't understand. How in the hell is it possible for an allocated object that has been leaked to cause an object that has not been leaked to confuse its references to other objects that have not been leaked?
No one is saying that.
Quote:
What's happening here is some object completely unrelated to any of the crashes is leaked. Then, these other objects that are just doing their thing, perfect reference containment and everything, are getting majorly confused about what's the Table View and what's a Table View Cell and what's a Search Results Display Controller.
No, that is not what is happening. Like Brian said a few posts ago, leaks do not cause crashes. I don't think you know what a leak is, or you would understand this. What you want to look for is a pointer (i.e. a instance variable or property) that got released and then used without an intervening reassignment or allocation of a new object. The pointer is still pointing to memory that may have been reassigned to some other object, because that memory was marked as "available" after it was released.
Just to further clarify, you aren't necessarily looking for a stray "release" in code. You could be, but those are generally easy to find. There's one, should that be released, yes/no, next one.
I will guess that what you are looking for is something autoreleased, and this is what I meant by "insufficiently retained". It will likely be something in either an assign property or something that used direct instance variable assignment. It should have been retained, but wasn't, and dies before you try to use it again.
Generally when I have odd behavious with UITableViews is when I accidentally requests a double whammy reload on the table view itself that will cause 2 or more reloadData notifications to be fired recursively.
IE: calling reloadData on a UITableViewDelegate function that will itself refresh the table.
I have no idea what's your code flow is like but I'll have a look at this nasty case.