I have a project using StoryBoards and UISearchDisplayController
used in the context of a UINavigationController
, that appears in the root viewcontroller. When I push a new view controller onto the stack and I cause a simulated memory warning (or actually get a low memory warning). The previous view controller successfully unloads its view. However, when I pop the second view controller off of the stack I get an EXC_BAD_ACCESS
. I turned on NSZombies and discovered this:
[UISearchDisplayController retain]: message sent to deallocated instance 0xb13aa30
I am not (at least in my code) sending that message to the UISearchDisplayController
. I am not doing anything, programmatically speaking, with it. Break points reveal that I am not even making it into the viewDidLoad
of the first view controller.
Something curious, though: for laughs and giggles I decided to outright retain
the SDC in my viewDidLoad
, just to see what would happen and no crash occurs. However, my UISearchDisplayController
instance is nil
.
I did a backtrace and get this output:
#0 0x01e30e1e in ___forwarding___ ()
#1 0x01e30ce2 in __forwarding_prep_0___ ()
#2 0x01dd1490 in CFRetain ()
#3 0x01eb69c0 in +[__NSArrayI __new::] ()
#4 0x01e0a00a in -[__NSPlaceholderArray initWithObjects:count:] ()
#5 0x01e34f52 in +[NSArray arrayWithObjects:count:] ()
#6 0x01e5e084 in -[NSDictionary allValues] ()
#7 0x01035272 in -[UINib instantiateWithOwner:options:] ()
#8 0x00edce2c in -[UIViewController _loadViewFromNibNamed:bundle:] ()
#9 0x00edd3a9 in -[UIViewController loadView] ()
#10 0x00edd5cb in -[UIViewController view] ()
#11 0x00edd941 in -[UIViewController contentScrollView] ()
#12 0x00eef47d in -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] ()
#13 0x00eef66f in -[UINavigationController _layoutViewController:] ()
#14 0x00eef93b in -[UINavigationController _startTransition:fromViewController:toViewController:] ()
#15 0x00ef03df in -[UINavigationController _startDeferredTransitionIfNeeded] ()
#16 0x00ef16cb in _popViewControllerNormal ()
#17 0x00ef196c in -[UINavigationController _popViewControllerWithTransition:allowPoppingLast:] ()
#18 0x0b446e82 in -[UINavigationControllerAccessibility(SafeCategory) _popViewControllerWithTransition:allowPoppingLast:] ()
#19 0x00ef0b10 in -[UINavigationController popViewControllerAnimated:] ()
#20 0x00ef297d in -[UINavigationController navigationBar:shouldPopItem:] ()
#21 0x00e7dabe in -[UINavigationBar _popNavigationItemWithTransition:] ()
#22 0x00e7da49 in -[UINavigationBar popNavigationItemAnimated:] ()
#23 0x0b42208c in -[UINavigationBarAccessibility(SafeCategory) popNavigationItemAnimated:] ()
#24 0x00e80507 in -[UINavigationBar _handleMouseUpAtPoint:] ()
#25 0x00e8074c in -[UINavigationBar touchesEnded:withEvent:] ()
#26 0x00e3fa30 in -[UIWindow _sendTouchesForEvent:] ()
#27 0x00e3fc56 in -[UIWindow sendEvent:] ()
#28 0x00e26384 in -[UIApplication sendEvent:] ()
#29 0x00e19aa9 in _UIApplicationHandleEvent ()
#30 0x02d37fa9 in PurpleEventCallback ()
#31 0x01e9e1c5 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#32 0x01e03022 in __CFRunLoopDoSource1 ()
#33 0x01e0190a in __CFRunLoopRun ()
#34 0x01e00db4 in CFRunLoopRunSpecific ()
#35 0x01e00ccb in CFRunLoopRunInMode ()
#36 0x02d36879 in GSEventRunModal ()
#37 0x02d3693e in GSEventRun ()
#38 0x00e17a9b in UIApplicationMain ()
#39 0x00002b72 in main (argc=1, argv=0xbffff620)
There doesn't appear to be anything really interesting there (is there ever? :P) and appears to be all internals to Apple's stuff. Any ideas on how to get this problem to go away?
UPDATE: Even when I remove the connection between my view controller and the property for the Search Display Controller but create my own IBOutlet
for it, it still crashes. Bad bug perhaps?
UPDATE 2: When I programmatically create my own instance of a UISearchDisplayController
(not through the storyboard) and create it in viewDidLoad
, everything works the way it is supposed to.
UPDATE 3: I am able to consistently reproduce this problem in a new project with a storyboard. I did the same thing using a vanilla nib and everything worked the way it was suppose to. However, if I setup the same thing using a storyboard and segue, it blows up just like it does in my real project. :(
RECAP: Here are the steps in recreating this issue:
- Create a view controller in a storyboard with a
UISearchDisplayController
- Push a new view controller on the navigation stack
- Cause a low memory warning
- Pop the controller off of the stack
- KABOOM!
viewDidLoad
does not even get called on the first view controller at this point, Apple's code blows up before then.
Here is what I did (granted, it's a workaround and not a fix for Apple's bug):
First, in a base
UIViewController
I created a property calledsearchController
:I added a
UISearchBar
in through interface builder so that I have a placeholder in my UI for it. Then, in myviewDidLoad
I manually setup the controller and wire it up:In my
viewDidUnload
I make sure to clear it out:An important point to note, is that crashes like this can occur if you leave the view whilst your searchDisplayController is still active. This is the issue I was having, selecting an item in the searchDisplayController was set to pop the view controller from the stack, in order to fix this, I had to include the following code before the view was popped...
So far, I found this working solution for iOS 5 SDK using ARC:
in .h file, declare your own searchDisplayController property with IBOutlet
Then in the .m file, synthesize it:
But don't set it to nil in viewDidUnload.
So that the search display controller will use the property you create instead of using the inherited property.
I also notice the similar bug also appear for gesture recognizers (if you create gesture recognizers from the storyboard instead of creating them programmablly). We also need to create STRONG gesture recognizer properties and hook them with the gesture recognizer objects that you create in storyboard. Then in viewDidUnload, don't set them to nil. <-- this prevent the crashes.
Why dont you use self.searchDisplayController only?
I have already used that many times it wont create any problem. You can also customize that if you want.
@Wayne: I had run into the same issue with a SearchDisplayController created from a Storyboard, and spend over a day trying to debug a crash that seemed to appear when none of my code was running. In my case the symptom was the user taps a tab in UITabBarController to return to a ViewController that has been unloaded after a memory warning. The unloaded view controller's viewDidLoad method never runs and the code gets at least as far as tabBarController:didSelectViewController: (which should run after viewDidLoad) before it crashes somewhere in the assembly code!
Thanks massively for posting this workaround and for all the follow-ups. A small improvement is to move your UIDisplayController instantiation to a lazily loaded accessor method for the searchDisplayController property. The practical effect is negligible but it looks nicer!