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