參考資料
iOS: 聊聊 Designated Initializer(指定初始化函數(shù))
《編寫高質量iOS與OS X代碼的52個有效方法》中第16條:提供“全能初始化方法”
對象的創(chuàng)建
在 Objective-C 中,對象的創(chuàng)建分為兩步埠帕,分配內存和初始化成員變量室叉。
NSObject *object = [[NSObject alloc] init];
首先調用類方法+ alloc
,其根據(jù)要創(chuàng)建的實例對象對應的類來分配足夠的內存空間照宝。除了分配內存空間,其實+ alloc
方法還做了其他事情句葵,包括將對象的引用計數(shù)記為1厕鹃,將對象的isa
指針指向對應的運行時類對象兢仰,以及將對象的成員變量置為對應的0值(0、nil剂碴、NULL)把将。
+ alloc
方法返回的對象還是不可用的,在之后完成初始化方法的調用后忆矛,對象的創(chuàng)建工作才算完成察蹲。初始化方法會設置對象的成員變量為一個正確的合理的值,以及獲取一些其他額外的資源洪碳。
對象的初始化
Designated Initializer 指定初始化方法
所有對象都是要初始化的递览,而且很多情況下,對象在初始化時是需要接收額外的參數(shù)瞳腌,這就可能會提供多個初始化方法绞铃。根據(jù)規(guī)范,通常選擇一個接收參數(shù)最多的初始化方法作為指定初始化方法嫂侍,真正的數(shù)據(jù)分配和其他相關初始化操作在這個方法中完成儿捧。而其他的初始化方法則作為便捷初始化方法去調用這個指定初始化方法。這樣當實現(xiàn)改變時挑宠,只要修改指定初始化方法就可以了菲盾。便捷初始化方法接收的參數(shù)更少,它會在內部調用指定初始化方法時各淀,直接設置未接收參數(shù)的默認值懒鉴。便捷初始化方法也可以不直接調用指定初始化方法,它可以調用其他便捷初始化方法碎浇,但不管調用幾層临谱,最終是要調用到指定初始化方法的,因為真正的實現(xiàn)操作是在指定初始化方法中完成的奴璃。所有初始化方法統(tǒng)一以- init
開始悉默。
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
如上例代碼所示,- initWithTimeIntervalSinceReferenceDate
方法是一個指定初始化方法苟穆,而其他初始化方法最終是要調用它的抄课。
子類實現(xiàn)指定初始化方法
當子類繼承父類后實現(xiàn)了新的指定初始化方法,此時如果調用父類中的指定初始化方法則無法調用到子類新實現(xiàn)的初始化邏輯雳旅,所以子類同時還要重寫父類的指定初始化方法跟磨,將其變?yōu)橐粋€便捷初始化方法,最終去調用子類自己的指定初始化方法攒盈。而為了保證父類初始化邏輯的執(zhí)行抵拘,在子類指定初始化方法中,首先要通過關鍵字super
調用父類的指定初始化方法沦童。
@interface Rectangle : NSObject
@property (nonatomic) float width;
@property (nonatomic) float height;
@end
@implementation Rectangle
- (instancetype)init {
return [self initWithWidth:5 Height:5];
}
- (instancetype)initWithWidth:(float)width Height:(float)height {
if (self = [super init]) {
self.width = width;
self.height = height;
}
return self;
}
@end
@interface Square : Rectangle
@end
@implementation Square
- (instancetype)initWithWidth:(float)width Height:(float)height {
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}
- (instancetype)initWithDimension:(float)dimension {
return [super initWithWidth:dimension Height:dimension];
}
@end
如上例代碼所示仑濒,Rectangle
類繼承自NSObject
類,它實現(xiàn)了新的指定初始化方法- initWithWidth:Height:
偷遗,而- init
方法是NSObject
類的指定初始化方法墩瞳,如果不重寫則該初始化方法不會設置新增屬性的值。所以在Rectangle
類中的- init
方法通過self
關鍵字調用了指定初始化方法氏豌。而在指定初始化方法中喉酌,通過[super init]
調用了父類的初始化邏輯。同理泵喘,Square
類又繼承自Rectangle
類泪电,它新實現(xiàn)了指定初始化方法- initWithDimension:
, 并調用了父類的指定初始化方法,所以要重寫方法- initWithWidth:Height:
纪铺,而此時因為- init
方法已重寫為便捷方法相速,會最終調用到新的指定初始化方法,所以不需要重寫了鲜锚。
在子類實現(xiàn)新的指定初始化話方法時突诬,除了將父類的指定初始化方法重寫為便捷方法外,也可以在重寫實現(xiàn)中拋出異常芜繁,即告訴外界在子類中是不提供這種初始化方式的旺隙。
如果子類不需要實現(xiàn)自己的指定初始化方法,或者子類的指定初始化方法就是重寫父類的指定初始化方法骏令,則其他的子類便捷初始化方法蔬捷,就調用子類中這個與父類指定初始化方法的同名方法即可。
- initWithCoder:
框架中的很多類實現(xiàn)了<NSCoding>
協(xié)議(如:UIViewController
)榔袋,這個協(xié)議定義了初始化方法- initWithCoder:
周拐,一般這個方法里的初始化邏輯與其他的指定初始化方法中是不同的,如UIViewController
通過該方法解碼 XML 格式的 NIB 文件摘昌。所以子類中可以有不止一個指定初始化方法速妖,- initWithCoder:
也是一個指定初始化方法。- initWithCoder:
中的相關實現(xiàn)規(guī)則是聪黎,如果父類也實現(xiàn)了<NSCoding>
協(xié)議罕容,首先要調用父類的- initWithCoder:
方法,如果父類沒有實現(xiàn)稿饰,則調用父類的指定初始化方法锦秒。
NS_DESIGNATED_INITIALIZER
當在接口中指定初始化方法的后面加上該宏,編譯器就會檢查我們實現(xiàn)的初始化調用鏈是否符合規(guī)則喉镰,并提示相應的警告旅择。另外NS_DESIGNATED_INITIALIZER
也起到了標明指定初始化方法的注釋作用。
- (instancetype)init NS_DESIGNATED_INITIALIZER;
總結
指定初始化方法的機制保證了對象會依次執(zhí)行從父類到子類的所有初始化邏輯侣姆,實現(xiàn)的規(guī)則為:
- 便捷初始化方法只能調用本類中的其他初始化方法生真,并最終調用到指定初始化方法沉噩。
- 子類的指定初始化方法要調用父類的指定初始化方法,以保證父類的初始化邏輯可以執(zhí)行柱蟀。
- 當子類實現(xiàn)了自己的指定初始化方法后川蒙,父類的指定初始化方法要重寫為便捷初始化方法,以保證所有初始化方法都能調用到子類的初始化邏輯长已。