如果一个类没有记录指定的初始化程序,会发生什么?

9
我正在寻求如何处理这种情况的一般指导。这里有一个具体的例子。
我正在对UIImageView进行子类化,我想覆盖initWithImage方法,在超类使用提供的图像自身初始化后添加我的自己初始化代码。
然而,UIImageView没有记录指定的初始化程序,那么应该调用哪个超类初始化程序以确保正确初始化我的子类?
如果一个类没有指定一个初始化程序,我应该:
  1. 假设调用类的任何(UIImageView)初始化程序是安全的?
  2. 查找超类(UIView)的指定的初始化程序?
在这种情况下,似乎#1是答案,因为在我的重写初始化器中进行以下操作是有意义的:
- (id)initWithImage:(UIImage *)image
{
    self = [super initWithImage:image];
    if (self) {
        // DO MY EXTRA INITIALIZATION HERE
    }
    return self;
}

我认为那看起来很好。你正在调用超类的初始化程序。问题在哪里?Objective-C没有提供调用指定超类初始化程序的机制。(据我所知) - Hermann Klecker
UIImageView 将是您的超类。您可以使用 super 调用该类的任何有效初始化程序,包括它可能继承的初始化程序(尽管其中一些可能没有意义)。 - Hot Licks
3
确实,这个方法很有效,但我的问题更加普遍。我理解子类应该总是调用父类的指定初始化器,但如果没有记录下来,我怎么知道什么是指定初始化器呢?如果没有标记为“指定初始化器”,在父类中调用任何初始化器是否安全? - techsMex
不知道你所说的“指定”。你必须调用超类的初始化程序,但没有一个是“指定”的。 - Hot Licks
5个回答

5

UIImageView有两个初始化器,因此您可能希望确保您的子类处理这两种初始化路径。

您可以简单地声明-initWithImage:您指定的初始化器,而所有其他初始化器都不受支持。

此外,您可以实现-initWithImage:highlightedImage:并抛出异常以表示不支持它。

或者,您可以声明-initWithImage:highlightedImage:为您的指定初始化器,并使-initWithImage:调用您的指定初始化器。

或者,您可能会发现您的-initWithImage:初始化器被调用,无论您的类是使用-initWithImage:还是-initWithImage:highlightedImage:初始化的。


Darren,你说UIImageView有2个初始化器,但是我怎么知道哪一个是UIImageView的指定初始化器?如果没有标记为“指定”的话,它们是否都作为指定初始化器? - techsMex
UIImageView没有指定的初始化程序(可能是因为第二个初始化程序直到iOS 3.0才添加)。如果有的话,那么通常你会重写这个初始化程序并覆盖所有基础内容。但由于它没有指定的初始化程序,您可能希望处理两个初始化程序并将它们都直接指向您自己的指定初始化程序。否则,如果有人无意中调用了您不支持的初始化程序,那么可能会产生难以跟踪的错误。 - Darren
@Darren - 好的,显然你在这种情况下使用“指定”的意思是指规范中描述的作为其他类的“父级”(我自己的术语)的init方法。例如,可能有一个initWithX:方法和一个initWithX:andY:方法。通常情况下,第一个方法(被称为“便利”方法)会调用第二个方法,因此您只需要重写第二个方法。规范可能会告诉您这一点,也可能不会。如果没有告诉您,您需要重写所有可能在使用您的类时被调用的init方法,但如果您知道只有一个方法被使用,那就只重写那个方法即可。 - Hot Licks
2
@HotLicks,你需要在苹果文档中搜索“指定初始化方法”这个术语。它不是虚构的术语,而是Cocoa中一个重要的概念。链接 - Ken Thomases

4

UIImageView文档很糟糕。它展示了两个初始化器,但在某些情况下,它们都不会被调用。例如,我正在使用IB,只有initWithCoder:被调用。

- (id)init
{
    return [super init];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    return [super initWithCoder:aDecoder];
}

- (id)initWithFrame:(CGRect)frame
{
    return [super initWithFrame:frame];
}

- (id)initWithImage:(UIImage *)image
{
    self = [super initWithImage:image];
    return self;
}

- (id)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
{
    self = [super initWithImage:image highlightedImage:highlightedImage];
    return self;
}

唯一正确的子类化UIImageView的方法是子类化所有初始器,并且每个子类只能使用相同名称调用父类的初始器。例如: subclass -init 可以调用 UIImageView -init,但不能调用 UIImageView -initWithCoder:
是的,没有指定确实很痛苦。

这是一个常见的问题 - 如果对象是从 NIB 文件加载的,则会调用 -initWithCoder: 方法。因此,您通常也需要覆盖它。 - nielsbot
3
我不相信这种方法是安全的。因为没有一个初始化函数被指定,你就无法确定对super的调用是否会回到另一个重写函数。如果它们不是微不足道的,那么你可能会做两次本不应该做两次的工作。(如果它们微不足道,你就不需要实现任何一个初始化函数。) - Ken Thomases

2
没有指定的初始化器时的危险在于,你可能调用的任何初始化器都可能按照另一个初始化器的术语完成其工作。在这种情况下,它可能会意外地调用到你的某个覆盖,而它不会按预期工作。
如果你的类恰好有一个初始化器并且它是超类初始化器的覆盖,则安全地调用该初始化器进行初始化。这是因为超类初始化器不会(直接或间接地)重新进入自身,所以不会重新进入你的覆盖方法。
你的类也可以实现任意数量的初始化器,只要没有与超类中的任何初始化器具有相同的名称。由于你的名称是唯一的,因此不存在任何超类初始化器意外调用到你的初始化器的可能性。

1

每个继承自NSObject的类都有一个初始化器init方法,用于对该对象进行初始化。因此,如果您不确定,您可以在自定义初始化器中始终使用self = [super init]。考虑到苹果提供了两个初始化器UIImageView,您可能需要重写它们或向用户抛出异常,告诉他们不能使用这种方法(不建议)。

例如:

- (id)initWithCustomParam:(NSString *)param {

    if (self = [super init]) {
        self.myparam = param;
    }
    return self;
}

然后您可以实现其他初始化程序,如下所示:

- (id)initWithImage:(UIImage *)image {

    if (self = [self initWithCustomParam:@"default value"]) {
        self.image = image;
    }
    return self;
}

或者定义,

- (id)initWithImage:(UIImage *)image customParam:(NSString *)string {

    if (self = [self initWithCustomParam:string]) {
        self.image = image;
    }
    return self;
}

0

另一种方法是懒加载。您可以使用像viewDidLoad或viewDidMoveToSuperview这样的方法来进行一些设置。这取决于何时设置很重要。


1
这完全没有回答问题。 - Aaron Brager
当然可以。它是一个懒惰地进行设置的地方。 - uchuugaka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接