View Controller being sent a message even though it has been deallocated
I am not sure if something has changed in the iPhone SDK 3.0 but I am getting the strangest error. I have a view controller hierarchy where I switch between view controllers depending upon interface orientation. From what I can tell, the error is caused whenever I rotate the interface a view controller which has been deallocated is being sent a shouldAutorotateToInterfaceOrientation message. This is the backtrace for the error:
#0 0x01dc43a7 in ___forwarding___
#1 0x01da06c2 in __forwarding_prep_0___
#2 0x002e6733 in -[UIWindow _shouldAutorotateToInterfaceOrientation:]
#3 0x002e6562 in -[UIWindow _updateToInterfaceOrientation:duration:force:]
#4 0x002e6515 in -[UIWindow _updateInterfaceOrientationFromDeviceOrientation]
#5 0x0004d63a in _nsnote_callback
#6 0x01d8f005 in _CFXNotificationPostNotification
#7 0x0004aef0 in -[NSNotificationCenter postNotificationName:object:userInfo:]
#8 0x0045b454 in -[UIDevice setOrientation:]
#9 0x002d6890 in -[UIApplication handleEvent:withNewEvent:]
#10 0x002d16d3 in -[UIApplication sendEvent:]
#11 0x002d80b5 in _UIApplicationHandleEvent
#12 0x024c2ef1 in PurpleEventCallback
#13 0x01d9bb80 in CFRunLoopRunSpecific
#14 0x01d9ac48 in CFRunLoopRunInMode
#15 0x024c17ad in GSEventRunModal
#16 0x024c1872 in GSEventRun
#17 0x002d9003 in UIApplicationMain
#18 0x00002d50 in main at main.m:14
The error that is getting printed to the Debug Console with NSZombieEnabled is:
2009-10-18 20:28:34.404 Restaurants[12428:207] *** -[ToolbarController respondsToSelector:]: message sent to deallocated instance 0x3b2b2a0
(gdb) continue
Current language: auto; currently objective-c
2009-10-18 20:31:43.496 Restaurants[12428:207] *** NSInvocation: warning: object 0x3b2b2a0 of class '_NSZombie_BeltToolbarController' does not implement methodSignatureForSelector: -- trouble ahead
2009-10-18 20:31:43.496 Restaurants[12428:207] *** NSInvocation: warning: object 0x3b2b2a0 of class '_NSZombie_BeltToolbarController' does not implement doesNotRecognizeSelector: -- abort
What I can't understand is why the system is trying to message this controller even though it has been deallocated and is there a way to tell th开发者_开发知识库e system that the controller doesn't exist any longer.
[UPDATE]: I have put together a sample project replicating the bug: download
Load up the app and then change the Simulator's orientation a few times from Landscape to Portrait and it should occur. I have tried the same piece of code on a physical phone and it behaves in exactly the same way, so this is not a simulator related issue.
[UPDATE]: I have used up one of my support requests with Apple's technical team to see if they can help me get to the bottom of this. Will post the solution - if they have one - here. Thanks for the help so far.
Evidently, the controller is still registered for the notification in question. send -removeObserver:self to the notification center in your -dealloc method.
So after a week of waiting, Apple Developer Technical Support managed to help me sort my problem out. Here is their response:
"I've looked over your code and found a few things you need to be concerned about, some of which may contribute to your problem. In your
ControllerSwitchAppDelegate.m
source, you are implementing "didRotate
" method. It might be worth checking for device orientation notifications at the view controller level rather than at the UIApplication level. This will make your code much more simpler and encapsulated allowing each view controller that is shown to handle its own rotation logic. You are also using multiple view controllers at the same time, that being, both "view" properties are being added and remove when the device is rotated. This is not exactly the common approach in which to use the UIKit. The idea is to present one view controller (or its view property) at a time and not have a parent view controller swap in different sub-view controllers. Granted on the surface your approach seems doable, but in the long run, I recommend a different approach.We have a sample called "AlternateViews", which can be found at - http://developer.apple.com/iphone/library/samplecode/AlternateViews/index.html
In this sample, it pretty much does what you need. It provides an "alternate" view controller for a given device orientation. Is merely presents one view controller over another using "presentModalViewController" with a transition property called "modalTransitionStyle" which will give you a cross fade affect."
What I ended up doing was using a super view controller that presented and dismissed view controllers. Rather than swapping view controllers and removing sub views using the AppDelegate.
Lots of confusion here... let's see if this helps:
What I can't understand is why the system is trying to message this controller even though it has been deallocated and is there a way to tell the system that the controller doesn't exist any longer.
While deallocation destroys the instance of an object, it doesn't destroy the references to the instances. With Zombie detection enabled, deallocation causes the runtime to substitute a zombie for the instance. The zombie then detects and logs when it is messaged.
The reason why this happens is because the object has been deallocated without also removing all references to the object from the application's object graph. In this case, it looks like the deallocated controller was never unset as the controller from the UIWindow instance.
That is, the processing of the notification is a red herring. In that backtrace, the notification has already been delivered and UIWindow is in the midst of processing it. Thus, the problem is somewhere between the UIWindow and your application. Most likely, your object has been deallocated before being removed from the window as its controller or delegate.
Before your program is truly done with an object -- before you send the last -release
call that balances the last existing -retain
your application caused or called -- you must make sure that all weak references to the object are destroyed. For example, if your object is the delegate of a UIWindow, make sure you set the delegate of the window to nil
(or some other object) before sending that last -release
.
Now, in this case, it may also simply be that you have over-released the object. You might still need the object around, but an extra -release
or -autorelease
somewhere is causing it to be destroyed before you were done with it.
If anyone still cares, a simple solution is to create a root view controller + view that never changes.
Given SomeViewController + SomeView A, and SomeViewController + SomeView B, if you add view A to the window as a subview, then add view B as a subview and remove view A, the app will crash on rotate.
To prevent the crash, create a generic UIViewController + UIView X, and add view X to the window as a subview. Add and remove views A and B to/from view X, not the window directly. The app will no longer crash.
Note that it's not enough to just add a view to the window; you must add a view that has a view controller.
This is a good reason to set the ToolbarController = nil
after releasing it. It's safe to send messages to nil, but not to addresses of deallocated objects. In this case your sending a message to an address of an object that doesn't exit, which is causing a crash.
It's a waste of time to check for the ToolbarController != nil
before sending the message, because if it's nil, than you can send the message safely. if it is not nil and valid, then it will return YES or NO. But if it's a pointer to deallocated memory (such as you seem to have here) it's going to crash anyway.
I've run into this problem as well. The order of events is:
(1) create the application's single UIWindow object
(2) add a subview that's managed by a view controller to the window
(3) remove the first view and add a new one
If I rotate the device afterwards, the application crashes because of a message sent to the deallocated view controller. (Well, it's actually sending it to a sub-controller of the first view controller.) It's trying to send -[respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)].
If your application only runs in portrait mode, you can make the problem go away by adding a category to UIWindow that overrides _shouldAutorotateToInterfaceOrientation: to return NO for everything other than portrait mode.
Obviously, this isn't a real solution. I've double-triple-checked my code and I can find no reason why the window should be sending this message to the controller for a view that's been removed from the screen and deallocated. This problem also seems to have appeared in 3.0 and not before. Perhaps I'm doing something stupid, but there really seems to be something strange at work here.
I also had the same problem but wasn't able to leave a controller hanging around as Mark Smith's suggestion. Removing the view controller with autorelease rather than release or a retained property seemed to do the trick.
Seems that the parent UIWindow/framework needs the view controller to hang around a little while longer to allow it to remove the delegate link.
I had been experiencing the same problem until deleted some 'bumping' code lines I used to push animation like those:
UIView* superv = navigationController.view.superview;
[navigationController.view removeFromSuperview];
[superv addSubview:navigationController.view];
Definitely, the above is 'breaking' way to go since 3.0 SDK had been issued by Apple. I've been forced to use push/pop approach instead. Didn't have the problem with 2.x as well. Make sure you don't have something similar in your code.
Hope, it helps.
I have posted an answer here:
https://stackoverflow.com/a/19237139/539149
I had a place that said:
[viewController release];
viewController = NULL;
Which caused release to be called twice (so the memory was freed immediately) but the zombie wasn't revealed until an object owned by iOS tried to reference the object later in the main thread.
精彩评论