How are views supposed to reload after being nillified by memory warnings?

  • Hi all.

    View controllers that are buried in the navigation stack (or otherwise have
    their views obscured) set their views to nil when they receive a memory
    warning.  This makes sense temporarily, because the views aren't visible.

    But when the overlapping views are dismissed, how is the nillified view
    supposed to reload itself?  Currently, ours doesn't, and this leaves a
    blank white screen and a permanently disabled app (until the user
    force-quits).

    I've found that viewWillAppear is not called on the blanked view
    controller, as it is when no memory warning has occurred.  So when exactly
    are these blanked views supposed to recover?

    This view is loaded from a XIB, by the way.

    Thanks for any insight!

    Gavin
  • The Apple doc says,

    "If the view controller has an associated nib file, this method loads the
    view from the nib file. A view controller has an associated nib file if the
    nibName<https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVie
    wController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewCont
    roller/nibName
    >
    property
    returns a non-nil value, which occurs if the view controller was
    instantiated from a storyboard, if you explicitly assigned it a nib file
    using the initWithNibName:bundle:<https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVie
    wController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewCont
    roller/initWithNibName:bundle:
    >
    method,
    or if iOS finds a nib file in the app bundle with a name based on the view
    controller’s class name. If the view controller does not have an associated
    nib file, this method creates a plain
    UIView<https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVie
    w_Class/UIView/UIView.html#//apple_ref/occ/cl/UIView
    >
    object
    instead."

    So I did override loadView, simply to write to a log and then call [super
    loadView].  In the log, I verified that the controller's loadView method IS
    being called after the overlapping view is dismissed.  I also verified that
    the object's nibName property contains the name of the XIB it's initialized
    with.  So it should be reloading from this XIB.  Instead, however, I'm
    getting the "plain" (blank) view.
  • > Date: Tue, 28 Feb 2012 22:36:38 -0800
    > From: G S <stokestack...>
    > To: cocoa-dev <Cocoa-dev...>
    >
    >
    > So I did override loadView, simply to write to a log and then call [super
    > loadView].

    If you override loadView you must *not* call super. To put it another way, don't implement loadView unless you mean it (i.e. you intend that your view will be set here, or a generic view should be supplied here). To put it another way, he who lives by messing with the framework dies by messing with the framework; part of the way the framework decides what you want is by looking to see what methods you've implemented, so you can't play fast and loose like this. m.

    --
    matt neuburg, phd = <matt...>, http://www.apeth.net/matt/
    pantes anthropoi tou eidenai oregontai phusei
    Programming iOS 5! http://shop.oreilly.com/product/0636920023562.do
    RubyFrontier! http://www.apeth.com/RubyFrontierDocs/default.html
    TidBITS, Mac news and reviews since 1990, http://www.tidbits.com
  • On Feb 28, 2012, at 10:36 PM, G S wrote:

    > So I did override loadView, simply to write to a log and then call [super
    > loadView].  In the log, I verified that the controller's loadView method IS
    > being called after the overlapping view is dismissed.  I also verified that
    > the object's nibName property contains the name of the XIB it's initialized
    > with.  So it should be reloading from this XIB.  Instead, however, I'm
    > getting the "plain" (blank) view.

    If your view controller is backed by a XIB, then yes you should just get that XIB reloaded and displayed.

    Do you do anything special inside of -viewDidLoad?
    --
    David Duncan
  • Thanks guys.

    The problem happens with no override of loadView. I only overrode it to
    verify that it was being called after the memory warning, and to verify
    that nibName was set.

    In viewDidLoad, I just instantiate a data collection and progress indicator.
  • Well, I've verified that the view controller has the correct name of the
    nib when it tries to reload the view after a memory warning.

    So this seems like a pretty big Cocoa bug, which unfortunately only one
    (remote) person on our team can reproduce reliably.  I know Instagram
    encountered this same problem, but I don't know what they did to work
    around it.
  • well in viewDidLoad take a look at view and the view hierarchy, where is it, what size is it, what alpha is it etc etc.

    On Mar 1, 2012, at 5:38 AM, G S wrote:

    > Thanks guys.
    >
    > The problem happens with no override of loadView. I only overrode it to
    > verify that it was being called after the memory warning, and to verify
    > that nibName was set.
    >
    > In viewDidLoad, I just instantiate a data collection and progress indicator.
  • OK, after our controller's view gets blown away on the memory warning, it
    does appear to be reloaded from the nib when it's time for redisplay.  At
    least the IBOutlet members are non-nil in viewDidLoad (I nilled them on
    viewDidUnload).

    So it appears that the controller's view is reloaded from the nib, but
    being displayed as a blank white screen.  The white view itself is
    screen-sized and has no superview.  What the hell?
  • so keep debugging then.

    How do you know the white view is screen-sized and has no superview? Actually a UIView which you can see and yet has no superview is probably just the UIWindow itself.

    How about the view which has just been loaded? Does it have a superview? Probably won't at that point, it's only been loaded, not moved into place. Go override the viewWillAppear:/viewDidAppear: methods and see if they get called.

    One question, there's no non-standard UIViewController usage here right? ie you have just ONE view controller for each screen, not trying to use more than one on the screen at the same time (with the exception of course of a tab or navigation controller which handles that). Transitions which put the new VCs on the screen are the usual presentModalViewController .. or push methods right? What's showing on the screen when this viewcontroller of yours is getting blown away by a memory warning, and how did that new content get there?

    On Mar 2, 2012, at 6:08 AM, G S wrote:

    > OK, after our controller's view gets blown away on the memory warning, it
    > does appear to be reloaded from the nib when it's time for redisplay.  At
    > least the IBOutlet members are non-nil in viewDidLoad (I nilled them on
    > viewDidUnload).
    >
    > So it appears that the controller's view is reloaded from the nib, but
    > being displayed as a blank white screen.  The white view itself is
    > screen-sized and has no superview.  What the hell?
  • >
    > How do you know the white view is screen-sized and has no superview?
    > Actually a UIView which you can see and yet has no superview is probably
    > just the UIWindow itself.
    >

    I write info about it to a log, in viewDidLoad.

    How about the view which has just been loaded? Does it have a superview?

    That is the white view.

    Probably won't at that point, it's only been loaded, not moved into place.
    > Go override the viewWillAppear:/viewDidAppear: methods and see if they get
    > called.
    >

    I'll try that.

    One question, there's no non-standard UIViewController usage here right? ie
    > you have just ONE view controller for each screen

    Yep.  It's a very straightforward structure.  Someone taps on a tableview
    row, and we push the controller onto the navigation stack.

    What's showing on the screen when this viewcontroller of yours is getting
    > blown away by a memory warning, and how did that new content get there?
    >

    It's the photo-picker (actually a view that presents the photo picker and
    then lets the user add a caption).  It's presented with
    presentModalViewController and dismissed by the delegate, as is typical.

    The Apple photo picker often results in memory warnings (based on various
    forum posts I've seen).  It's after the dismissal of the picker's owning
    controller that the white screen is revealed.

    Thanks a lot for the follow-up.

    Gavin
  • On Mar 2, 2012, at 1:28 AM, G S wrote:

    > It's the photo-picker (actually a view that presents the photo picker and
    > then lets the user add a caption).  It's presented with
    > presentModalViewController and dismissed by the delegate, as is typical.
    >
    > The Apple photo picker often results in memory warnings (based on various
    > forum posts I've seen).  It's after the dismissal of the picker's owning
    > controller that the white screen is revealed.

    In the vast majority of cases where I've seen this behavior, it is because in your delegate handler for the UIImagePickerController, you assign the returned image directly to a UIImageView that you have in your view hierarchy. If you've recently gotten a memory warning, then this image view is either nil, or will be released very soon, and you will end up with a view that has no image.

    If this does turn out to be the case, then lesson learned is "never store critical data in my views".
    --
    David Duncan
  • On Fri, 02 Mar 2012 10:17:43 -0800, David Duncan <david.duncan...> said:
    > On Mar 2, 2012, at 1:28 AM, G S wrote:
    >
    > In the vast majority of cases where I've seen this behavior, it is because in your delegate handler for the UIImagePickerController, you assign the returned image directly to a UIImageView that you have in your view hierarchy. If you've recently gotten a memory warning, then this image view is either nil, or will be released very soon, and you will end up with a view that has no image.

    So let's say I have a view controller. And let's say that its view (self.view) contains a UIImageView in its hierarchy (self.view.imageview). You are saying that the runtime might summarily rip the UIImageView out of the interface, so that self.view is *not* nil but self.view.imageview *is* nil???

    m.

    --
    matt neuburg, phd = <matt...>, <http://www.apeth.net/matt/>
    A fool + a tool + an autorelease pool = cool!
    Programming iOS 5! http://shop.oreilly.com/product/0636920023562.do
  • >
    > In the vast majority of cases where I've seen this behavior, it is because
    > in your delegate handler for the UIImagePickerController, you assign the
    > returned image directly to a UIImageView that you have in your view
    > hierarchy. If you've recently gotten a memory warning, then this image view
    > is either nil, or will be released very soon, and you will end up with a
    > view that has no image.
    >

    Thanks for that observation, David.  But the entire screen is white;
    there's not even the status bar visible.  Our app doesn't have any
    full-screen UIImageView.  The only one involved in this process is confined
    to part of the screen.

    I wonder if there's a bug somewhere that's revealed by our particular
    scenario, which consists of these steps:

    1. We're on the root view of our tab (the root view of the navigation
    controller that lives on this tab).  It shows a UIListView of items.

    2. The user opts to create a new item, so we display a modal item-creation
    screen that subsequently brings up (and handles the dismissal of) the photo
    picker.  While this is up, memory warnings often occur.

    3. After the user completes the item-creation process, our modal
    item-creation controller calls its delegate (the original list screen) to
    say it's done.

    4. The delegate creates an item-detail view for the new item, and pushes it
    onto the navigation stack (behind the modal view).  It then dismisses the
    modal view, which should reveal the newly created detail screen.  This
    works fine, unless a memory warning happened; in that case only a white
    screen is revealed and the app essentially hangs because the user can't
    interact with it.

    What we know from logging is that (in the memory-warning case)

    1. The detail screen's view is loaded from the XIB, but its viewWillAppear
    method is never called.
    2. The tab's root view (the list) is unloaded, and its viewWillAppear
    method is never called.

    But our modal view's viewWillDisappear method IS called.  So no view is
    told that it's about to appear when the modal one disappears.
  • Are you allowed to push a view controller onto a navigation controller which is hidden by something modal and has had its top level VCs view removed? If I was doing that and getting what you're getting I would suspect that and change it so that either

    1 the new view controller is pushed by the viewDidDisappear of the dismissed modal one or

    2 knowing the view is just about to be rebuilt, override the memory warning handling not to dump the views, at least to that first level.

    I would certainly have tried those as a first debugging step.

    On 5 Mar, 2012, at 9:39, G S <stokestack...> wrote:

    >>
    >> In the vast majority of cases where I've seen this behavior, it is because
    >> in your delegate handler for the UIImagePickerController, you assign the
    >> returned image directly to a UIImageView that you have in your view
    >> hierarchy. If you've recently gotten a memory warning, then this image view
    >> is either nil, or will be released very soon, and you will end up with a
    >> view that has no image.
    >>
    >
    > Thanks for that observation, David.  But the entire screen is white;
    > there's not even the status bar visible.  Our app doesn't have any
    > full-screen UIImageView.  The only one involved in this process is confined
    > to part of the screen.
    >
    > I wonder if there's a bug somewhere that's revealed by our particular
    > scenario, which consists of these steps:
    >
    > 1. We're on the root view of our tab (the root view of the navigation
    > controller that lives on this tab).  It shows a UIListView of items.
    >
    > 2. The user opts to create a new item, so we display a modal item-creation
    > screen that subsequently brings up (and handles the dismissal of) the photo
    > picker.  While this is up, memory warnings often occur.
    >
    > 3. After the user completes the item-creation process, our modal
    > item-creation controller calls its delegate (the original list screen) to
    > say it's done.
    >
    > 4. The delegate creates an item-detail view for the new item, and pushes it
    > onto the navigation stack (behind the modal view).  It then dismisses the
    > modal view, which should reveal the newly created detail screen.  This
    > works fine, unless a memory warning happened; in that case only a white
    > screen is revealed and the app essentially hangs because the user can't
    > interact with it.
    >
    > What we know from logging is that (in the memory-warning case)
    >
    > 1. The detail screen's view is loaded from the XIB, but its viewWillAppear
    > method is never called.
    > 2. The tab's root view (the list) is unloaded, and its viewWillAppear
    > method is never called.
    >
    > But our modal view's viewWillDisappear method IS called.  So no view is
    > told that it's about to appear when the modal one disappears.
  • Thanks for the feedback, Roland.

    On Sun, Mar 4, 2012 at 6:07 PM, Roland King <rols...> wrote:

    > I would suspect that and change it so that either
    >
    > 1 the new view controller is pushed by the viewDidDisappear of the
    > dismissed modal one or
    >

    This would require excessive knowledge of the delegate controller on the
    part of the modal controller.

    2 knowing the view is just about to be rebuilt, override the memory warning
    > handling not to dump the views, at least to that first level.
    >

    That was an early consideration, but I've seen forum posts from people who
    did this and found it was not reliable (sometimes the views were dumped
    anyway).  Plus, I don't mind letting the memory-conservation mechanism
    work; my controllers should be able to handle the reload.  It's just that
    the pertinent controller's view is never shown.

    My next step will probably be to get rid of the creation of the detail
    controller temporarily, to see whether the underlying root controller
    recovers from the memory warning properly.  If it does, I guess the
    workaround will be to wait for the dismissed modal controller to disappear
    before pushing the detail controller.  Too bad, though, because that's
    going to look janky.
  • On Mar 3, 2012, at 8:57 AM, Matt Neuburg wrote:

    > On Fri, 02 Mar 2012 10:17:43 -0800, David Duncan <david.duncan...> said:
    >> On Mar 2, 2012, at 1:28 AM, G S wrote:
    >>
    >> In the vast majority of cases where I've seen this behavior, it is because in your delegate handler for the UIImagePickerController, you assign the returned image directly to a UIImageView that you have in your view hierarchy. If you've recently gotten a memory warning, then this image view is either nil, or will be released very soon, and you will end up with a view that has no image.
    >
    > So let's say I have a view controller. And let's say that its view (self.view) contains a UIImageView in its hierarchy (self.view.imageview). You are saying that the runtime might summarily rip the UIImageView out of the interface, so that self.view is *not* nil but self.view.imageview *is* nil???

    No. But if you've presented a modal view controller, your entire view hierarchy (self.view) has been ripped out of the window, and if a memory warning arrives, then self.view will be set to nil. What happens next depends on if you implemented -viewDidUnload properly or if your view references are strong or weak.

    If they are strong and you did not implement -viewDidUnload properly, then your view reference will be non-nil. On the next load however, that reference will be replaced by a newly created view. If they are weak, or you implemented -viewDidUnload properly, then the view reference will be nil, and the usual message to nil semantics will take over.
    --
    David Duncan
  • >
    > No. But if you've presented a modal view controller, your entire view
    > hierarchy (self.view) has been ripped out of the window, and if a memory
    > warning arrives, then self.view will be set to nil. What happens next
    > depends on if you implemented -viewDidUnload properly or if your view
    > references are strong or weak.
    >
    > If they are strong and you did not implement -viewDidUnload properly, then
    > your view reference will be non-nil.

    Not according to the Apple doc.  By the time viewDidUnload is called, the
    view reference has already been set to nil:

    "viewDidUnload

    Called when the controller’s view is released from memory.
    - (void)viewDidUnload
    Discussion

    When a low-memory condition occurs and the current view controller’s views
    are not needed, the system may opt to remove those views from memory. This
    method is called after the view controller’s view has been released and is
    your chance to perform any final cleanup. If your view controller stores
    separate references to the view or its subviews, you should use this method
    to release those references. You can also use this method to remove
    references to any objects that you created to support the view but that are
    no longer needed now that the view is gone. You should not use this method
    to release user data or any other information that cannot be easily
    recreated.
    At the time this method is called, the view property is nil. "
  • On Mar 5, 2012, at 3:40 PM, G S wrote:

    > No. But if you've presented a modal view controller, your entire view hierarchy (self.view) has been ripped out of the window, and if a memory warning arrives, then self.view will be set to nil. What happens next depends on if you implemented -viewDidUnload properly or if your view references are strong or weak.
    >
    > If they are strong and you did not implement -viewDidUnload properly, then your view reference will be non-nil.
    >
    > Not according to the Apple doc.  By the time viewDidUnload is called, the view reference has already been set to nil:

    I think you are confusing what I mean by "view reference" at the end there. Consider this interface:

    @interface ViewController : UIViewController

    @property (nonatomic, /* strong or weak */) UIView *subview;

    @end;

    My statement above refers to what happens to self.subview, not to self.view (hence why I used self.view the first time, then view reference the second time).
    --
    David Duncan
  • Well, at any rate, I have no such subview property.

    I've also found that among my series of modal presentations, there's a view
    that's displayed modally, whose user interaction works just fine, but whose
    viewWillAppear and viewWillDisappear methods are never called.  As far as I
    know, it could be this one that's displaying the white view.

    At this point it's clear that Cocoa is confused by something; perhaps
    because some of these transitions are animated and not completed when a new
    one starts.

    As a last resort, I'm going to have to refactor this entire sequence to
    push the views onto the navigation controller, but cancel the sequence and
    pop them all if the user hits the Back button before furnishing required
    information.
previous month february 2012 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29        
Go to today