欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Objective-C对象的初始化(2)——便利初始化函数

程序员文章站 2022-05-20 10:34:14
...

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