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方法效果非常不错,那就不一定要去创建初始化函数;
  • 如果创建了一个指定初始化函数,则一定要在自己的指定初始化函数中调用超类的指定初始化函数;
  • 如果初始化函数不止一个,则需要选择一个作为指定初始化函数;
  • 被选定的方法应该调用超类的的指定初始化函数;
  • 要按照指定初始化函数的形式实现所有其他初始化函数。

猜你喜欢

转载自blog.csdn.net/qq_33737036/article/details/81323508