【问题标题】:Initialising Objects in Objective-C在 Objective-C 中初始化对象
【发布时间】:2012-06-13 23:52:38
【问题描述】:

我无法理解 Stephen Kochan 的“Objective-C 编程”一书第 4 版中的以下内容。我希望你能帮助我理解它。

在第 10 章的“初始化对象”部分,Stephen 写道:

在编写初始化程序时,您应该遵循以下两种策略。

当你的类中的一个对象被初始化时,你可能想要做一些特别的事情。例如,这是创建类使用和通过一个或多个实例变量引用的对象的理想场所。一个完美的例子就是我们的 Rectangle 类;在 init 方法中分配矩形的 XYPoint 原点是合理的。为此,我们只需要重写继承的 init 方法。

有一个用于覆盖 init 的标准“模板”,它看起来像这样:

- (id) init
{
     self = [super init];
     if (self) {
          // initialisation code here.
     }

     return self;
}

此方法首先调用父初始化程序。执行父级的初始化程序可确保正确初始化任何继承的实例变量。

您必须将执行父级的 init 方法的结果分配回自身,因为初始化程序有权更改对象在内存中的位置(意味着其引用将更改)。

如果父级的初始化程序成功,则返回的值将非零,正如 if 语句所测试的那样。正如注释所示,在后面的块内,您可以为您的对象放置您自己的自定义代码。这通常涉及分配和初始化类中的实例变量。

好的,到目前为止,我已经理解了 Stephen Kochan 想要表达的意思,但我完全被下一部分迷住了。我希望你能帮忙。

如果您的类包含多个初始化程序,其中一个应该是您指定的初始化程序,并且所有其他初始化方法都应该使用它。通常,这是您最复杂的初始化方法(通常是采用最多参数的方法)。

所以,我的第一个问题是:如果所有其他初始化方法在这种情况下都将使用一个特定的“指定”初始化器,为什么还要使用它们?

Stephen Kochan 继续说:

创建一个指定的初始化程序将您的主要初始化代码集中在一个方法中。任何继承您的类的人都可以覆盖您指定的初始化程序,以确保正确初始化新实例。

你能举个例子吗?我不太确定我明白他在说什么。

斯蒂芬继续:

基于该讨论,您的 Fraction 类的初始化方法 initWith:over: 可能如下所示:

- (Fraction *) initWith:(int)n over:(int)d
{
    self = [super init];

    if (self) {
        [self setTo: n over: d];
    }

    return self;
}

在 super 的初始化(以及它的成功,如返回一个非零值所示)之后,您使用 setTo:over: 方法来设置您的分数的分子和分母。与其他初始化方法一样,您应该返回已初始化的对象,您在此处执行此操作。

程序 10.1 测试您的新 initWith:over: 初始化方法。

#import "Fraction.h"

int main (int argc, char *argv[])
{
    @autoreleasepool {
        Fraction *a, *b;

        a = [[Fraction alloc] initWith: 1 over: 3];
        b = [[Fraction alloc] initWith: 3 over: 7];

        [a print];
        [b print];

    }

    return 0;
}

输出:
1/3
3/7

到目前为止,我已经理解了代码。以下部分我完全看不懂:

要遵守前面关于指定初始化程序的规则,您还应该在 Fraction 类中修改 init。如果您的类可能是子类,这一点尤其重要。

下面是 init 方法的样子:

- (id)init
{
    return [self initWith:0 over:0];
}

如果我们想要子类化,为什么这很重要?

Stephen Kochan 继续说道:

当您的程序开始执行时,它会将初始化调用方法发送到您的所有类。如果您有一个类和关联的子类,则父类首先获取消息。该消息只向每个类发送一次,并且保证在任何其他消息发送到该类之前发送。目的是让您在此时执行任何类初始化。例如,您可能想在那时初始化一些与该类关联的静态变量。

我也没有真正理解最后一部分。我希望你能提供帮助。

【问题讨论】:

  • 回答者将在下面的答案中这样做;他们不会给你发电子邮件。 (我已经删除了你提到的问题部分。)
  • 从已出版的书中引用这么多文本和代码是否存在版权问题(或 SO 政策问题)?似乎比提出这个问题所需要的要多得多;这会延伸通常的合理使用规则吗?
  • @abarnert 我不这么认为,但是 IANAL。我看过引用更多的大学水平的论文,所以我认为只要它明显被引用就可以了,并且有一些讨论可以进行。

标签: objective-c initialization nsobject


【解决方案1】:

这是一大堆问题,很难回答。

所以,我的第一个问题是:如果所有其他初始化方法在这种情况下都将使用一个特定的“指定”初始化器,为什么还要使用它们?

主要是为了方便您的来电者。例如,假设您指定的初始化程序是 initWithX:y:width:height:,但您发现自己到处都在写这样的东西:

[[MyRect alloc] initWithX:0 y:0 width:0 height:0]
[[MyRect alloc] initWithX:myPoint.x y:myPoint.y width:mySize.width height:mySize.height]

您可能想要添加另外几个初始化程序,所以您可以这样做:

[[MyRect alloc] initWithEmptyRect]
[[MyRect alloc] initWithPoint:myPoint size:mySize]

当然,它实际上从来没有必要,但值得这样做的原因与将某些东西包装在一个函数中而不是一遍又一遍地重复自己总是值得的原因相同。

通过查看 Foundation/Cocoa 附带的各种类,您可以找到更实际的示例——其中许多具有许多不同的初始化程序,而且它们通常包含更多的工作,而不仅仅是调用 .x、.y、.width , 和 . 高度。例如,NSDictionary 有像-initWithContentsOfURL: 这样的方法。理论上,您始终可以读取该 URL 的内容,将 plist 解析为一对 C 样式的对象和键数组,然后调用 -initWithObjects:forKeys:count:,因此这实际上不是必需的。但你更愿意做什么?

“创建一个指定的初始化器集中你的主 单一方法中的初始化代码。任何子类化你的类的人 然后可以覆盖您指定的初始化程序以确保新的 实例已正确初始化”。

你能举个例子吗?我不太确定我是否理解 他在说什么。

假设有人创建了这个类:

@interface MySuperRect: MyRect
- (id)initWithX:x y:y width:width height:height;
@end

他不需要重写你所有的 init 方法,只需要这个。假设你这样做:

[[MySuperRect alloc] initWithEmptyRect]

因为 MySuperRect 没有实现 initWithEmptyRect,这将使用来自 MyRect 的实现,它只是调用指定的初始化程序。但是 MySuperRect 已经实现了指定的初始化器。因此,将调用覆盖。

我认为这也回答了您的第三个问题。 (我认为是第三个问题。)

对于您的第四个也是最后一个问题,您需要先说明您不理解的部分,然后才能有人帮助您。例如,您是否知道类只是一种(稍微)特殊的对象,并且通常会了解对象初始化的工作原理,并了解类初始化的特殊之处,但不明白为什么要在那里初始化静态变量?

【讨论】:

    【解决方案2】:

    如果在这种情况下所有其他初始化方法都将使用一个特定的“指定”初始化器,为什么还要使用它们?

    为方便起见,您会这样做。您的“指定”初始化程序(正如 Stephen Kochan 提到的)通常是具有最多参数的初始化程序。所有其他初始化程序都是使用一些默认设置调用该初始化程序的便捷方法。例如,如果我有一个“汽车”类,我可以在其中指定各种元素的数量:

    @implementation Car
    
    //Designated initializer - has tons of arguments
    - (id)initWithDoors:(int)doors windows:(int)windows wheels:(int)wheels axles:(int)axles
    {
        //implementation
    }
    
    //most axles have two wheels and most doors have a window (and there's the front and back window)
    - (id)initWithDoors:(int)doors wheels:(int)
    {
        return [self initWithDoors:doors windows:doors+2 wheels:wheels axles:wheels/2];
    }
    
    //most cars have four doors, four wheels, six windows, and two axles
    - (id)init
    {
        return [self initWithDoors:4 windows:6 wheels:4 axles:2];
    }
    
    @end
    

    然后,子类只调用指定的初始化程序。这是 Coupe 类的示例。 (双门轿车有两扇门。)

    @implementation Coupe //extends Car
    
    //our coupe can have a sunroof, which adds a window
    - (id)initWithSunroof:(BOOL)sunroof
    {
        self = [super initWithDoors:2 windows:(sunroof?4:5) wheels:4 axles:2];
        if (self) {
          //initialization
        }
    
        return self;
    }
    
    //a default coupe has no sunroof
    - (id)init
    {
        return [self initWithSunroof:NO];
    }
    
    @end
    

    注意-initWithSunroof: 方法是子类的指定初始化器。

    如果我们想要子类化,为什么这很重要?

    最后,您想实现-init,这样一个天真的子类就可以调用它并获取所有默认值,而无需过多地研究您的类(如果不需要的话)。

    我也没有真正理解最后一部分。

    类初始化器的格式如下:

    + (void)initialize
    {
        //implementation
    }
    

    它设置您可能拥有的任何类级变量。这些影响一个类的所有实例。 (类变量和实例变量之间的区别在其他地方得到了很好的解释,超出了本答案的范围。如果您仍然感到困惑,请提出另一个问题。)

    【讨论】:

    • +1 提供了比我的答案更详细的示例。当然,您只能回答原始混乱中嵌入的 4 个或更多单独问题中的 1 个,但这更多是提问者的错而不是您的错。
    • @abarnert 我尝试在编辑中添加一些其他问题。
    • 太糟糕了,我不能再次 +1 你的答案。我认为您现在已经涵盖了所有内容,既简洁又详细。
    • @abarnert 看起来你无论如何都“赢了”。我认为我们的答案可以很好地结合在一起,因为我们涵盖的内容略有不同。
    【解决方案3】:

    “最复杂的初始化程序”是具有最多参数的初始化程序。您应该使所有其他初始化方法都使用该方法:
    可能有initWithValueA:andB:andC: 以及initWithValueC:initWithValueA:initWithValueB:andC: 等等。所有那些“不太复杂”的方法都应该使用所有其他参数的默认值调用“最复杂”的方法(可能是 0、nil、...)
    [super init] 将调用超类的 init-method,因此您可以安全地覆盖 init 以调用“最复杂”的 init-Method 并设置您的默认状态或抛出异常 - 如果您想调用自定义 @ 987654328@你必须使用[self init])。

    您提到的最后一部分涵盖了静态初始化程序(“静态类构造函数”)。它的声明以“+”号开头,这意味着它是一个类方法(“静态方法”),如 alloc。在 C# 中是 static MyClass() {...},在 Java 中是 static { ... }

    【讨论】:

    • 这不是一个真正的答案。你只是在重申他已经引用的内容。他在问他为什么要其他 init 方法,而你只需告诉他如何编写它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-07
    • 1970-01-01
    • 2013-07-06
    • 2010-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多