View Controller being sent a message even though it has been deallocated

12,863

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
12,863
Michael Gaylord
Author by

Michael Gaylord

iOS Engineer at Civic Inc. Co-founder of Storie Inc. Founding engineer at Gyft Inc.

Updated on June 04, 2022

Comments

  • Michael Gaylord
    Michael Gaylord almost 2 years

    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.

  • Michael Gaylord
    Michael Gaylord over 14 years
    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.
  • Felixyz
    Felixyz over 14 years
    This is almost certainly it. I've had the same problem.
  • bbum
    bbum over 14 years
    Almost assuredly entirely unrelated. The object is still registered to receive notifications even though it is deallocated.
  • bbum
    bbum over 14 years
    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.
  • SinisterMJ
    SinisterMJ over 14 years
    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...
  • Michael Gaylord
    Michael Gaylord over 14 years
    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
    Michael Gaylord over 14 years
    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
    Michael Gaylord over 14 years
    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.
  • NSResponder
    NSResponder over 14 years
    Filing a bug report with a small test case would be a good idea.
  • NSResponder
    NSResponder over 14 years
    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.
  • Michael Gaylord
    Michael Gaylord over 14 years
    I have added a link to a sample project replicating the problem. You can find the link at the bottom of the description above.
  • bbum
    bbum over 14 years
    File a bug and attach the sample project.
  • bbum
    bbum over 14 years
    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.
  • Michael Gaylord
    Michael Gaylord over 14 years
    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
    Michael Gaylord over 14 years
    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.
  • Mark Smith
    Mark Smith over 14 years
    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?
  • Michael Gaylord
    Michael Gaylord over 14 years
    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.
  • fulvio
    fulvio over 13 years
    Removing the view controller with autorelease for me seemed to do the trick as well. Thanks for the info!