2

我无法理解 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:方法来设置您的 Fraction 的分子和分母。与其他初始化方法一样,您应该返回已初始化的对象,您在此处执行此操作。

程序 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 继续说道:

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

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

4

3 回答 3

3

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

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

主要是为了方便您的来电。例如,假设您指定的初始化程序是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已经实现了指定的初始化器。因此,将调用覆盖。

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

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

于 2012-06-14T00:15:26.160 回答
1

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

你这样做是为了方便。您的“指定”初始化程序(正如 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
}

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

于 2012-06-14T00:21:41.020 回答
0

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

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

于 2012-06-14T00:11:04.280 回答