Objective-C对象的初始化(2)——便利初始化函数
0x01 便利初始化函数
有些对象拥有多个以init开头的方法名,这些方法和普通的方法一样,只是遵循命名规则约定,用init开头表示它用于初始化。
很多类中包含便利初始化函数(Convenience Initializer),它们是用来完成某些额外工作的初始化方法。
下面我们用NSString类中的一些初始化方法来举例。
1、基本的初始化方法,返回一个空字符串:
- (id) init;
//-----------------------------------------------------------------------
NSString *emptyString = [[NSString alloc] init];
//最基本的init对不可变的NSString类来说就仅仅是初始化一个空字符串
NSMutableString *emptyMutableString = [[NSString alloc] init];
//但初始化一个NSMutableString类的对象就可以开始向其中添加字符
2、带格式化操作的初始化方法,直接返回带格式的字符串,效果类似stringWithFormat:
- (id) initWithFormat: (NSString *) format, ...;
//-----------------------------------------------------------------------
string = [[NSString alloc] initWithFormat: @"%d or %d", 25, 624];
//This gives you a string with the value of "25 or 624".
这里可以看出Apple建议把alloc和init分开的原因,分配和初始化的功能是不一样的,把初始化步骤独立开来可以为程序带来更多灵活性。
作为Cocoa的编程人员应该熟练地使用alloc+init,而new只是辅助方法。
3、使用文件中的内容来初始化字符串:
- (id) initWithContentsOfFile:(NSString *) path
encoding:(NSStringEncoding) enc
error:(NSError **) error
//-----------------------------------------------------------------------
NSError *error = nil;
NSStringEncoding encoding = NSUTF8StringEncoding;
NSString *string = [[NSString alloc] initWithContentsOfFile:@"/tmp/words.txt"
usedEncoding:&encoding
error:&error];
if(nil != error)
{
NSLog(@"Unable to read data from file, %@", [error localizedDescription]);
}
- @"/tmp/words.txt"指定了文件路径,方法将在该文件读取内容并使用该内容初始化一个字符串;
- encoding参数将文件内容的类型告诉API,一般来说使用的是NSUTF8StringEncoding;
- error参数如果初始化正常则返回 nil, 否则返回错误信息。这些错误信息被封装成 NSError 对象,可以使用 NSError 的对象方法: localizedDescription来查明情况,并使用%@格式符打印错误信息;
- 注意这里的error是指针的指针!
0x02 初始化函数的使用
当我们的类对象中存在成员变量时,我们可以用初始化函数对其进行初始赋值。
以Tire类为例,我们将之扩展,用pressure记录胎压,treadDepth记录胎纹深度,所以接口部分修改如下:
#import <Cocoa/Cocoa.h>
@interface Tire : NSObject
{
float pressure;
float treadDepth;
} //Tire类多了两个成员变量,pressure是胎压,treadDepth是胎纹深度
//--------------------pressure的setter/getter方法
-(void) setPressure: (float) pressure;
-(float) pressure;
//--------------------treadDepth的setter/getter方法
-(void) setTreadDepth: (float) treadDepth;
-(float) treadDepth;
@end // Tire.h
在Tire类的实现中,为使用者提供了访问tire对象的方法,并提供了初始化函数,最后对description方法也进行了相应的修改:
#import "Tire.h"
@implementation Tire
- (id) init
{
if (self = [super init])
{
pressure = 34.0;
treadDepth = 20.0;
}
return (self);
} // init
//初始化完成后,新的tire对象将是带有预置参数值的新对象
- (void) setPressure: (float) p
{
pressure = p;
} // setPressure
- (float) pressure
{
return (pressure);
} // pressure
- (void) setTreadDepth: (float) td
{
treadDepth = td;
} // setTreadDepth
- (float) treadDepth
{
return (treadDepth);
} // treadDepth
- (NSString *) description
{
NSString *desc;
desc = [NSString stringWithFormat:@"Tire: Pressure: %.1f TreadDepth: %.1f", pressure, treadDepth];
return (desc);
} // description
@end // Tire.m
因为Tire类关键代码发生了变化,包含了Tire类的Car类也需要进行相应修改,在此就不详述了。
0x03 构造便利初始化函数
假设我们要用Tire类创建一个新的tire对象,main()函数节选代码如下:
...
tire = [[Tire alloc] init];
[tire setPressure: 23 + i];
[tire setTreadDepth: 33 - i];
...
为了创建一个新的对象,发送了4个消息,3行代码。
如果Tire类中的成员变量更多,只会让程序显得更加冗长。
为了更快、更方便地执行初始化,我们可以构造一个能同时获得成员变量的便利初始化函数:
@interface Tire : NSObject
{
float pressure;
float treadDepth;
}
//New Convenience Initializer
- (id) initWithPressure: (float) pressure treadDepth: (float) treadDepth;
- (void) setPressure: (float) pressure;
- (float) pressure;
- (void) setTreadDepth: (float) treadDepth;
- (float) treadDepth;
@end // Tire
该便利初始化函数的实现非常简单:
- (id) initWithPressure: (float) p treadDepth: (float) td
{
if (self = [super init]) {
pressure = p;
treadDepth = td;
}
return (self);
} // initWithPressure:treadDepth:
现在,main()函数中只需一行代码即可完成新tire对象的分配和初始化工作:
...
Tire *tire;
tire = [[Tire alloc] initWithPressure: 23 + i treadDepth: 33 - i];
...
0x04 便利初始化函数的缺点
在子类化的时候,为了保证超类能正常初始化,就需要重写超类所有的初始化方法和便利初始化方法,并添加子类成员变量的初始化方法。
但即使我们都重写了这些超类的方法,当超类修改或者添加初始化方法的时候,子类仍然需要做相应的修改。
这样的设计偏离了高内聚低耦合的设计原则!
0x05 指定初始化函数
类中的某个初始化方法被指派为指定初始化函数,该类的所有初始化方法都使用指定初始化方法执行初始化操作。
而子类使用其超类的指定初始化方法进行超类的初始化。
通常,接收参数最多的初始化方法是最终的指定初始化函数,通常它最灵活。
因此,对于子类来说,只要重写超类的指定初始化方法就可以解决便利初始化方法的缺点。
为了保证指定初始化函数的顺利执行,所有其他的初始化函数均应该按照initWithPressure:treadDepth:的形式实现:
首先修改Tire类:
#import "Tire.h"
@implementation Tire
- (id) init
{
if (self = [self initWithPressure: 34 treadDepth: 20]) {
//...
}
return (self);
} // init
- (id) initWithPressure: (float) p
{
if (self = [self initWithPressure: p treadDepth: 20.0]) {
//...
}
return (self);
} // initWithPressure
- (id) initWithTreadDepth: (float) td
{
if (self = [self initWithPressure: 34.0 treadDepth: td]) {
//...
}
return (self);
} // initWithTreadDepth
- (id) initWithPressure: (float) p treadDepth: (float) td
{
if (self = [super init]) {
pressure = p;
treadDepth = td;
}
return (self);
} // initWithPressure:treadDepth:
再向Tire类的子类AllWeatherRadial类添加指定初始化函数:
#import "AllWeatherRadial.h"
@implementation AllWeatherRadial
// 指定初始化函数
- (id) initWithPressure: (float) p treadDepth: (float) td
{
self = [super initWithPressure: p treadDepth: td]; //重写超类的指定初始化函数
if (self) {
rainHandling = 23.7;
snowHandling = 42.5;
} //添加子类的成员变量值
return (self);
}
//-----------------------------------------------------------------------
- (void) setRainHandling: (float) rh
{
rainHandling = rh;
} // setRainHandling
- (float) rainHandling
{
return (rainHandling);
} // rainHandling
- (void) setSnowHandling: (float) sh
{
snowHandling = sh;
} // setSnowHandling
- (float) snowHandling
{
return (snowHandling);
} // snowHandling
...
0x06 子类也可以有指定初始化函数
同理,子类也能给自己指定初始化函数,这样就能让用户在初始化的时候决定自己要的成员变量值:
...
//子类也可以有自己的指定初始化函数
- (id)initWithSnowHanding:(float)s andRainHanding:(float)r
{
self = [super init];
if (self) {
self.snowHandling = s;
self.rainHandling = r;
}
return self;
}
...
相应的,在mian()函数中初始化新的tire对象,可以同时调用该initWithSnowHanding:andRainHanding:方法:
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Car *car = [[Car alloc] init];
for (int i = 0; i < 4; i++)
{
AllWeatherRadial *tire;
//tire = [[AllWeatherRadial alloc] init];
//使用子类的指定初始化函数设置成员变量值
tire = [[AllWeatherRadial alloc] initWithSnowHanding:11.0 andRainHanding:22.0];
[car setTire: tire atIndex: i];
}
Engine *engine = [[Slant6 alloc] init];
[car setEngine: engine];
[car print];
}
return 0;
}
//Output:
//AllWeatherRadial: 34.0 / 20.0 / 22.0 / 11.0
//AllWeatherRadial: 34.0 / 20.0 / 22.0 / 11.0
//AllWeatherRadial: 34.0 / 20.0 / 22.0 / 11.0
//AllWeatherRadial: 34.0 / 20.0 / 22.0 / 11.0
//I am a slant-6. VROOOM!
//Program ended with exit code: 0
//
0x07 初始化函数规则
- 如果不需要设置任何状态,或者默认的alloc+init方法效果非常不错,那就不一定要去创建初始化函数;
- 如果创建了一个指定初始化函数,则一定要在自己的指定初始化函数中调用超类的指定初始化函数;
- 如果初始化函数不止一个,则需要选择一个作为指定初始化函数;
- 被选定的方法应该调用超类的的指定初始化函数;
- 要按照指定初始化函数的形式实现所有其他初始化函数。