在Objective-C中初始化对象

时间:2012-06-13 23:52:38

标签: objective-c initialization nsobject

我无法理解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方法的结果分配给self,因为初始化者有权更改内存中对象的位置(意味着它的引用会发生变化)。

     

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

好的,所以到目前为止我已经理解了斯蒂芬·科汉试图说的话,但我对下一部分完全不知所措。我希望你能提供帮助。

  

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

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

Stephen Kochan接着说:

  

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

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

斯蒂芬继续说道:

  

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

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

  

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

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

3 个答案:

答案 0 :(得分: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 已经实现了指定的初始化程序。因此,覆盖将被调用。

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

对于您的第四个也是最后一个问题,您需要在有人可以帮助您之前解释您不理解的部分内容。例如,你是否认为类只是一种(略微)特殊的对象,并且通常可以了解对象初始化的工作方式,并了解类初始化是如何特殊的,但不明白为什么要在那里初始化静态变量?

答案 1 :(得分:1)

  

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

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

@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
}

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

答案 2 :(得分:0)

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

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