【问题标题】:Custom init method in Objective-C, how to avoid recursion?Objective-C中的自定义init方法,如何避免递归?
【发布时间】:2010-12-25 17:29:15
【问题描述】:

我想创建一个 UINavigationController 的子类,它总是以相同的根控制器开始。没什么特别的,所以(对我来说)像这样重写 init 方法是非常有意义的:

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

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

   return self;
}

这显然会产生问题,因为[super initWithRootViewController]会调用[UINavigationController init],这当然是我们重写的init方法,所以会发生无限递归。

我不想创建具有不同名称的 init 方法,例如“initCustom”。

目前我只能想出一个解决方案,但我真的很讨厌这种 hack:

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

   initCalled = YES;

   rootController = [[MyController alloc] init];

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

   return self;
}

所以我的问题是:有没有更好的方法来处理这个问题?我确定我遗漏了一些非常明显的东西,但我没有看到。

编辑:我想这样做的原因,可以在下面我的一个 cmets 中看到:

我想创建一个始终以特定视图控制器开头的导航控制器。我想对班级的消费者隐藏这一点。不需要暴露的东西就不用暴露了。让生活更轻松,代码更简洁(发明封装的原因之一,对吧?)

【问题讨论】:

  • 你为什么要这样做,你想通过子类化做什么(与否决票无关)?

标签: iphone objective-c cocoa-touch uikit init


【解决方案1】:

首先 UINavigationController 不适合子类化。

无论如何,最简单的方法是覆盖initWithRootViewController:

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

你最好不要自动释放 MyController,但你理解这个想法......

【讨论】:

  • 为什么 UINavigationController 不能用于子类化?
  • 来自文档'这个类不用于子类化。相反,在您希望应用程序的用户界面反映内容的分层性质的情况下,您可以按原样使用它的实例。这意味着苹果可以随时改变内部行为。 (只是子类'init'还不错,对于核心子类更是如此)
  • +1 表示“UINavigationController 不适用于子类化”
  • 从 iOS 6 开始,您可以继承 UINavigationController。检查文档的第一段。
【解决方案2】:

我在这里阅读了很多关于为什么会这样的内容,但没有真正干净的解决方案。正如 Gcamp 所指出的,文档明确告诉我们不要继承 UINavigationController。

所以我开始思考:如果不允许子类化,那么我们只能使用封装,这似乎是解决这个问题的一个可接受的解决方案:

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

实施:

@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

我不是 Objective-C 方面的专家,所以这可能不是最好的方法。

【讨论】:

  • 如果你真的需要一个更真实的子类,你可以查看消息转发和NSProxy。我非常不鼓励你这样做。如果 Apple 告诉你不要子类化它,我不会……
  • 人们已经给了你一些解决方案。它们可能是“干净的”,也可能不是,因为您不应该子类化。但是在任何类中,如果您是子类,您应该在一个新的、专门的指定初始化程序(为您的子类)中进行初始化,或者通过覆盖父类的指定初始化程序来进行初始化。 init 不是 UINavigationController 的指定初始化器,因此您不应该覆盖它。
  • 听到这个“解释”,不难看出为什么很多人认为Objective-C是一门非常原始的语言。 Apple 构建了一个很棒的框架,但很遗憾它是建立在如此混乱的编程语言之上的。
  • 嗯,这是一种非常古老的语言,所以。只是出于好奇,为什么还要创建一个子类?
  • 我想创建一个始终以特定视图控制器开头的导航控制器。我想对班级的消费者隐藏这一点。不需要暴露的东西就不用暴露了。让生活更轻松,代码更简洁(发明封装的原因之一,对吧?)
【解决方案3】:

你真的测试过这段代码吗?为什么[super initWithRootViewController] 应该调用覆盖的init 方法?它将调用[super init] 方法,即(如您所说)[UINavigationController init](不是您覆盖的init)。

【讨论】:

  • 在内部,UINavigationController 很有可能从 initWithRootViewController 调用 init。
  • 我确实对此进行了测试。由于无限递归,第一个代码导致堆栈溢出。
  • [UINavigationController initWithRootViewController] 实际上调用了[self init],而不是[super init]
  • 它可能会调用自己的 init 方法来重用/共享该代码。
  • @Mike:他们在想什么?这就像违反了书中的每一条 OO 规则。
【解决方案4】:

只需覆盖并使用 -initWithRootViewController: 指定初始化程序。您可以将 nil 作为参数传递

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

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

   return self;
}

在这里阅读更多: http://developer.apple.com/mac/library/documentation/cocoa/conceptual/ObjectiveC/Articles/ocAllocInit.html#//apple_ref/doc/uid/TP30001163-CH22-106376

【讨论】:

  • 当然可以,但是如果你强制这个类的消费者以非标准的方式初始化对象(即只调用init),那不是很优雅
  • 总是覆盖指定的初始化器。您可以通过创建一个类方法来“隐藏”特定的初始化程序调用来使其更清晰。
  • 实际上,您不需要子类化 UINavigationController 只是为了添加将“MyViewController”封装为根的自定义初始化。只需向 UINavigationController 添加一个类别,例如:@interface UINavigationController(MyRootExtension) +(UINavigationController*)newControllerWithMyRoot; @end
  • 你不完全正确。 initWithRootViewController: 是 NOT 指定的初始化器。 This is a convenience method。 UINavigationController 的指定初始化器与超类的相同 -- initWithNibName:bundle:
猜你喜欢
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-12
  • 2021-05-13
  • 2014-09-10
相关资源
最近更新 更多