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方法效果非常不錯,那就不一定要去創建初始化函數;
  • 如果創建了一個指定初始化函數,則一定要在自己的指定初始化函數中調用超類的指定初始化函數;
  • 如果初始化函數不止一個,則需要選擇一個作爲指定初始化函數;
  • 被選定的方法應該調用超類的的指定初始化函數;
  • 要按照指定初始化函數的形式實現所有其他初始化函數。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章