前段時間準(zhǔn)備面試時温数,突然被問到一個問題:init:
和 initWithFrame:
方法應(yīng)該調(diào)用哪個?為什么蜻势?
其實(shí)這個問題涉及的是 Objective-C 語法的 指定初始化方法(Designated Initializer) 和 間接初始化方法(Secondary Initializer) 的相關(guān)知識撑刺。
指定初始化方法
類本身
一個類應(yīng)該只有一個 Designated Initializer,該初始化方法提供所有參數(shù)以供初始化握玛。其他初始化方法中應(yīng)該盡可能地調(diào)用 指定初始化方法够傍。
下面以知名開源庫 AFNetworking
為例,驗(yàn)證下 Designated Initializer 與 Secondary Initializer 之間的關(guān)系挠铲。
@interface AFURLSessionManager : NSObject
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
@end
@implementation AFURLSessionManager
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
// 以下代碼省略
....
}
...
@end
AFURLSessionManager
繼承自 NSObject
冕屯,除了默認(rèn)的 init:
方法外,還定義了 initWithSessionConfiguration:
指定初始化方法拂苹。
在其內(nèi)部實(shí)現(xiàn)中安聘,在 init:
方法中調(diào)用了 Designated Initializer。
其中 NS_DESIGNATED_INITIALIZER 是編譯器指令 __attribute__((objc_designated_initializer))
的宏定義瓢棒。用來標(biāo)記 指定初始化方法浴韭。
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
在一個類中,封裝 API 時脯宿,保證一個Designated Initializer念颈,Secondary Initializer 調(diào)用 Designated Initializer。
類繼承
那么對于繼承關(guān)系時连霉,應(yīng)該如何處理呢榴芳?Apple 有這么一句話:
the object should first invoke its superclass's designated initializer to initialize inherited state
在類繼承時調(diào)用任何 Designated Initializer 都是合法的嗡靡,而且應(yīng)該保證父類的 Designated Initializer 能夠得到調(diào)用,這樣會使繼承關(guān)系上的類都得以正確的初始化翠语。
雖然沒有這樣的明確規(guī)定叽躯,但 Apple 的框架都遵守這個約定财边,我們也應(yīng)該盡力遵守肌括。
當(dāng)定義一個新類 API 時,有時會有不同的方式:
- 不需要重寫任何初始化方法
- 重寫 Designated Initializer
- 定義一個新的 Designated Initializer
對于第一種酣难,不需要再做其他操作谍夭,只需要規(guī)范調(diào)用父類 Designated Initializer 方法即可。
對于第二種憨募,需要在重寫方法里調(diào)用 super 方法以初始化父類參數(shù)紧索,然后再進(jìn)行特定初始化。
對于第三種菜谣,確保調(diào)用了父類的 Designated Initializer珠漂,并且本類的 Secondary Initializer 調(diào)用自己的 Designated Initializer歪架。
依舊以 AFNetworking
為例存谎。AFHTTPSessionManager
繼承于 AFURLSessionManager
,并定義了自己的 Designated Initializer骡送。
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
+ (instancetype)manager;
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
// 指定初始化方法冈敛,且與父類 AFURLSessionManager 的指定初始化方法不同
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
@end
@implementation AFHTTPSessionManager
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
// 調(diào)用了父類的 Designated Initializer
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 省略一些代碼
....
return self;
}
#pragma mark - NSSecureCoding
- (instancetype)initWithCoder:(NSCoder *)decoder {
...
self = [self initWithBaseURL:baseURL sessionConfiguration:configuration];
if (!self) {
return nil;
}
...
return self;
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
return HTTPClient;
}
...
@end
從這里可以看出:
- 子類雖然定義了新的 Designated Initializer待笑,但是依然會調(diào)用父類的 Designated Initializer;
- 子類的 Secondary Initializer 調(diào)用了本類的 Designated Initializer抓谴。
間接初始化方法
對于間接初始化方法暮蹂,應(yīng)盡可能地調(diào)用 指定初始化方法,以保證最終調(diào)用的是 指定初始化方法癌压。調(diào)用時對于一些參數(shù)提供默認(rèn)值或 nil仰泻。
小結(jié)
- 指定一個 指定初始化方法,以盡可能多的初始化參數(shù)滩届,避免出現(xiàn)意外集侯;
- 對于一個類來說,間接初始化方法 的實(shí)現(xiàn)應(yīng)調(diào)用 指定初始化方法丐吓;
- 對于繼承時有三種情況:
-
不重寫任何初始化方法
正常調(diào)用
-
重寫指定初始化方法
調(diào)用父類指定初始化方法
-
自定義一個新的初始化方法
調(diào)用父類指定初始化方法浅悉,并本類遵循“間接初始化方法 的實(shí)現(xiàn)應(yīng)調(diào)用 指定初始化方法”
-
參考
https://github.com/oa414/objc-zen-book-cn/#designated-initializer
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html#//apple_ref/doc/uid/20000948-BCIHBJDE
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MultipleInitializers.html