views:

2434

answers:

9

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 the 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.

+1  A: 

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.

Abizern
I thought setting it to nil worked but it seems the error occurs less frequently. Will dig and see if I can find any other place that is releasing this object but not setting it to nil.
Michael Gaylord
Almost assuredly entirely unrelated. The object is still registered to receive notifications even though it is deallocated.
bbum
+3  A: 

Evidently, the controller is still registered for the notification in question. send -removeObserver:self to the notification center in your -dealloc method.

NSResponder
This is almost certainly it. I've had the same problem.
Felixyz
This is happening during processing of the notification, not in sending the actual notification itself. Looks to me more like the delegate or controller has been deallocated without first disconnecting it from the UI.
bbum
The postNotificationName:object:userInfo line of the stack trace disagrees strongly with bbum's assessment.When you send a notification by default the notification handler calls any listeners right away, before the post call even returns...
Kendall Helmstetter Gelner
The odd thing though is that the notification is being sent from the underlying system. So I haven't registered the controller for the notification. Adding removeObserver:self doesn't help, unfortunately.
Michael Gaylord
It isn't the notification that is the problem, but the processing of the notification (as the notification is already delivered in that backtrace). Specifically, that it is crashing trying to call respondsToSelector: is far more typical of a delegate or delegate-like pattern. I.e. you could quite likely trigger the same crash without a notification being in the backtrace by directly manipulating the UIWindow.
bbum
A: 

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.

bbum
The error doesn't occur immediately after the object has been deallocated but rather upon the second time the interface has been rotated. In other words, I rotate the interface from landscape to portrait, deallocate the controller, do stuff in portrait mode without any problems, then reorient the interface to landscape mode and then the error occurs.
Michael Gaylord
When there are references to a deallocated object, a crash can happen whenever something tries to send it a message. In your case it's happening when the orientation changes.
NSResponder
+1  A: 

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.

Mark Smith
This is definitely something new in 3.0 as my app never gave them in 2.2. Something has definitely changed in the SDK. I have also found that I now get CFRetain errors in internal code when I never used to in 2.2.
Michael Gaylord
Filing a bug report with a small test case would be a good idea.
NSResponder
I have added a link to a sample project replicating the problem. You can find the link at the bottom of the description above.
Michael Gaylord
File a bug and attach the sample project.
bbum
A: 

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.

Mike Orlov
Thanks for that Mike, but using a navigation controller isn't really an option as I need to keep my memory footprint as low as possible. I have sent my question to Developer Program Tech Support to see if they can help, because I truly think this is a bug in the framework. I will post my solution as soon as they get back to me.
Michael Gaylord
In some ways you were right, here. I ended up using the presentModalViewController method which worked really well. Instead of a navigation controller with pushing and popping of controllers.
Michael Gaylord
+1  A: 

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.

Michael Gaylord
Thanks for posting this.Maybe it's just me, but this doesn't seem like a very helpful answer from Apple. There's a big difference between "your approach seems doable, but in the long run, I recommend a different approach" and "here's why messages are being sent to deallocated view controllers in 3.0". Did they give you any more specifics, or was that it?
Mark Smith
That was pretty much the whole e-mail. I could have followed it up further but it did in the end solve my problem. I suspect that the reason it changed in 3.0 had something to do with performance improvements.
Michael Gaylord
+2  A: 

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.

Mark Smith
A: 

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.

gazreese
A: 

Hello... I'm dealing with this very same problem.

I have a Main View Controller, with several UIButtons on it and an UIView. On press, I remove the last selected view, allocate the new viewcontroller, and add it's view as a subview of the Main's view.

It's kinda an UITabController custom implementation.

Bad side... I get the same "respondsToaselector:" (should auto rotate).

I have really NO idea how to solve this.

Any suggestion will be more than welcome.

Thanks!!!!!

Jorge Leandro Perez
Take a look at the suggested solution I received from Apple's Dev Program support. I had to refactor a bit of my code, but it ultimately solved my problem.
Michael Gaylord
Thank you Michael. I'm gonna refactor my code, keep only one ViewController at the same time. I hope that way the issue will just disappears.(I'll change ViewControllers for plain views).Perhaps the UIWindow is detecting that some of the views being displayed belong to different ViewControllers, and automatically registers the 'other' viewcontrollers.Thank you..!!
Jorge Leandro Perez