1

I'm trying to create a class cluster as subclass of UIViewController to accomplish some points:

1. Different behavior of the ViewController depending on actual iOS version

2. iOS version checks don't clutter up the code

3. Caller doesn't need to care

So far I got the classes MyViewController, MyViewController_iOS7 and MyViewController_Legacy.

To create instances I call the method myViewControllerWithStuff:(StuffClass*)stuff which is implemented like:

+(id)myViewControllerWithStuff:(StuffClass*)stuff
{
    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
    {
        return [[MyViewController_iOS7 alloc] initWithStuff:stuff];
    }
    else
    {
        return [[MyViewController_Legacy alloc] initWithStuff:stuff];
    }
}

The caller uses myViewControllerWithStuff:. After that the so created view controller gets pushed onto a UINavigationController's navigation stack.

This nearly works as intended with one big downside: ARC doesn't dealloc the instance of MyViewController_xxx when it gets popped from the navigation stack. Doesn't matter which iOS version.

What am I missing?

UPDATE: -initWithStuff:

-(id)initWithStuff:(StuffClass*)stuff
{
    if (self = [super init])
    {
        self.stuff = stuff;
    }

    return self;
}

This method is also implemented in MyViewController. The differences kick in later (e.g. viewDidLoad:).

Stefan Jager
  • 141
  • 1
  • 10
  • 1
    Do you have a strong reference to it somewhere else in the code? The class cluster stuff seems like a red herring. It shouldn’t be causing problems. – Zev Eisenberg May 05 '14 at 14:06
  • On an unrelated note, you might want to return `instancetype` instead of `id` in order to get better compile-time type safety checks (against the superclass, at least). – Zev Eisenberg May 05 '14 at 14:07
  • The caller has a local reference `MyViewController *viewCtrl = ...` in an `IBAction` method and also in `collectionView:didSelectItemAtIndexPath:` which gets passed to the navigation controller. But there is no property or instance variable for it. – Stefan Jager May 05 '14 at 14:19
  • What is in your `initWithStuff` method? I tried your code snippet in a basic Master Detail Application and the custom view controllers had been deallocated. – Roland Kákonyi May 05 '14 at 14:19
  • Are you sure ARC is enabled? – Zev Eisenberg May 05 '14 at 14:30
  • "Objective-C Automatic Reference Counting" is set to YES for debug and release. ARC is disabled for some classes with "-fno-objc-arc", but not for the class cluster. – Stefan Jager May 05 '14 at 14:37
  • Is ARC disabled for the customers of the class cluster? – David Berry May 05 '14 at 16:06
  • By customer you mean the caller of `myViewControllerWithStuff:`? That is also ARC-enabled. If that's alright the way it is, I guess I have to do a deeper search than before :( I'll keep you updated... – Stefan Jager May 05 '14 at 18:11

1 Answers1

0

First of all: Thanks for all your help, comments, answers, suggestions...

Of course there was another strong reference to the MyViewController-object. But it wasn't that obvious, because it was not a property or instance variable.

In viewDidLoad I did the following:

[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    [self saveState];
}];

This should prevent data loss in case the user sends the app to the background. Of course the block captures the needed parts of its environment. In this case it captured self. The block keeps self alive until it (the block) gets destroyed, which is the case when e.g. [[NSNotificationCenter defaultCenter] removeObserver:self]; gets called. But, bad luck, this call is placed in the dealloc method of MyViewController which won't get called as long as the block exists...

The fix is as follows:

__weak MyViewController *weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
    [weakSelf saveState];
}];

Now the block captures weakSelf. This way it can't keep the MyViewController-object alive and everything deallocs and works just fine.

Stefan Jager
  • 141
  • 1
  • 10
  • Out of curiosity, if you put back the strong reference to `self` and run the static analyzer, does it catch the retain cycle? – Zev Eisenberg May 05 '14 at 22:41
  • Nope, nothing. The block could be retained by the Notification Center. In that case we don't have a cycle, but the block would still block the `dealloc` ceremony ;) – Stefan Jager May 05 '14 at 23:06