Search code examples
iosobjective-cuipopovercontroller

Popover controller got destroyed (nil) in iOS 8


In iOS 7 I used to define weak reference to popover controller inside my view controller (which displays in popover). I used this reference to popover controller in order to dismiss popover programmatically. Also, I defined delegate of popover controller in order to track dismissing events (popoverControllerShouldDismissPopover etc).

In iOS 8 it stops working. Investigation shows that weak reference point to nil after some point. The delegate stops working as well (as I understand it's because delegate was defined in popover controller which got destroyed in iOS 8 for some reason after popover displays).

Problem was solved by changed property to be strong reference. For some popovers (I have bunch of them) I had to add strong reference only for the reason to keep popoverController alive because I need the delegate to work. It's obvious hack. I added property which I don't really need nor use.

Could you please clarify if it's right approach. My concern is that strong reference may lead to memory leaks. Also I don't quite understand why popoverController get destroyed in iOS 8 while popover still on the screen.

This is my view controller with weak property. After changing weak to strong to start working well under iOS 8:

@interface TFDSuggestionViewController : UIViewController
...
@property (weak, nonatomic) UIPopoverController *myPopoverController;
...
@end

This is how I assign value to my property and delegate in prepareForSegue method in calling view controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"suggestions"]) {
        TFDSuggestionViewController *suggController = ((TFDSuggestionViewController *)[segue destinationViewController]);
        suggController.myPopoverController = ((UIStoryboardPopoverSegue *)segue).popoverController;
        ((UIStoryboardPopoverSegue *)segue).popoverController.delegate = self;
    }
} 

Thank you for you advice!


Solution

  • In the old days of manual memory management there was something called a reference count. It was essentially the number of times an object was retained (strong reference) by other objects or the App. In the more recent ARC (Automatic reference counting) we no longer need to do [object retain] and [object release]. These actions are handled for us by the compiler.

    Now to your situation. A weak reference does not increase the reference count of the object you are referencing. So if you create an object in a scope, assign a weak reference to it, then leave the scope your object's reference count is 0.

    -(void)SomeMethod
    {
        ClassObject *object = [[ClassObject alloc] init];
        //object's internal reference count is now 1
    
        self.myPopoverController = object;
        //Since myPopoverController is a weak reference, object still has reference count of 1
    
        //Some other code that does things and stuff.
    }
    //We just closed the scope, so object's reference count is now 0
    //ARC is free to release the object to free it's memory, causing any
    //weak references to return nil
    

    In the example above it shows a very simple object life cycle. Once you understand the life cycle you can see why a weak reference will do you absolutely no good in this situation.

    As to why it worked in iOS7 and not in iOS8 the only answer that I have is that iOS8 is likely much more efficient in garbage collection. If you ran it a million times in iOS7 I'm sure you would find at least one example of the exact same problem happening. It was a flaw in the code that the new OS makes more prevalent.

    If you want the object to stay alive you need to have at least one strong reference to it. The only precaution is that when you call dismiss you should nil the strong reference. Then there should be no adverse memory issues to resolve.

    Another bit that is very important. The UIPopoverController is not the same object as what is visible on screen. What is visible on screen is the UIPopoverController.view. The view is still retained by the view hierarchy, but the controller needs to be retained by you in order for it not to get released. Once the UIPopoverController is released the view's delegate will be nil since view.delegate is also a weak reference.

    Study the object lifecycle. It will help you avoid garbage collection problems that will definitely arise in the future as the OS gets more and more efficient in memory handling.