我无法理解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继续说道:
当您的程序开始执行时,它会将初始化调用方法发送到您的所有类。如果您有一个类和关联的子类,则父类首先获取该消息。此消息仅向每个类发送一次,并且保证在将任何其他消息发送到类之前发送。目的是让您在此时执行任何类初始化。例如,您可能希望在此时初始化与该类关联的一些静态变量。
我也没理解这最后一部分。我希望你能提供帮助。
答案 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 { ... }