简体   繁体   中英

Custom init method in Objective-C, how to avoid recursion?

I want to create a subclass of UINavigationController which always starts with the same root controller. Nothing special, so (to me) it makes perfect sense to override the init method like this:

- (id) init {
   rootController = [[MyController alloc] init];

   if (self = [super initWithRootViewController:rootController]) {
       // do some more initialization
   }

   return self;
}

This obviously creates a problem, because [super initWithRootViewController] will call [UINavigationController init], which is of course our overridden init method, so infinite recursion will occur.

I don't want to create an init method with a different name like "initCustom".

There's currently only one solution I can come up with, but I really hate this kind of hack:

- (id) init {
   if (initCalled)
       return self;

   initCalled = YES;

   rootController = [[MyController alloc] init];

   if (self = [super initWithRootViewController:rootController]) {
       // do some more initialization
   }

   return self;
}

So my question is: is there a better way of handling this? I'm sure I'm missing something very obvious, but I don't see it.

EDIT : The reason why I want to do this, as can be seen in one of my comments below:

I want to create a navigation controller that always starts with a specific view controller. I want to hide this from the consumer of the class. No need to expose things that don't need exposing. Makes life a lot easier, and code a lot cleaner (one of the reasons encapsulation was invented, right?)

First of all UINavigationController is not intended for subclassing.

Anyway, the simplest way to do that is to override initWithRootViewController:

- (id) initWithRootViewController:(UIViewController) viewController {
   return [super initWithRootViewController:[[[MyController alloc] init] autorelease]];
}

You better don't autorelease MyController, but you understand the idea...

I've read a lot of things here on WHY this behaves like this, but no real clean solution. As Gcamp pointed out, the documentation explicitly tells us not to subclass UINavigationController.

So I started thinking: if subclassing is not allowed, that leaves us with encapsulation, which seems like an acceptable solution to solve this:

@interface MyNavigationController : UIViewController {
   UINavigationController *navController;
   UIViewController *myController;
}

Implementation:

@implementation MyNavigationController

- (id) init {
   if (self = [super init]) {
       myController = [[MyController alloc] init];
       navController = [[UINavigationController alloc] initWithRootViewController:myController];
   }

   return self;
}

- (void) loadView {
   self.view = navController.view;
}

- (void) dealloc {
   [navController release];
   [myController release];
   [super dealloc];
}

@end

I'm not an expert in Objective-C, so this may not be an the best way to do this.

Did you actually test this code? Why should [super initWithRootViewController] call the overriden init method? It will call the [super init] method, which is (as you said) [UINavigationController init] (not your overriden init ).

Just override and use -initWithRootViewController: designated initializer. You can pass nil as argument

- (id) initWithRootViewController:(UIViewController*)ignored {
   rootController = [[MyController alloc] init];

   if (self = [super initWithRootViewController:rootController]) {
       // do some more initialization
   }

   return self;
}

Read more here: http://developer.apple.com/mac/library/documentation/cocoa/conceptual/ObjectiveC/Articles/ocAllocInit.html#//apple_ref/doc/uid/TP30001163-CH22-106376

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM