init:、initWithFrame: 调用选择
前段时间准备面试时,突然被问到一个问题:init:
和 initWithFrame:
方法应该调用哪个?为什么?
其实这个问题涉及的是 Objective-C 语法的 指定初始化方法(Designated Initializer) 和 间接初始化方法(Secondary Initializer) 的相关知识。
指定初始化方法
类本身
一个类应该只有一个 Designated Initializer,该初始化方法提供所有参数以供初始化。其他初始化方法中应该尽可能地调用 指定初始化方法。
下面以知名开源库 AFNetworking
为例,验证下 Designated Initializer 与 Secondary Initializer 之间的关系。
@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
,除了默认的 init:
方法外,还定义了 initWithSessionConfiguration:
指定初始化方法。
在其内部实现中,在 init:
方法中调用了 Designated Initializer。
其中 NS_DESIGNATED_INITIALIZER 是编译器指令 __attribute__((objc_designated_initializer))
的宏定义。用来标记 指定初始化方法。
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
在一个类中,封装 API 时,保证一个Designated Initializer,Secondary Initializer 调用 Designated Initializer。
类继承
那么对于继承关系时,应该如何处理呢?Apple 有这么一句话:
the object should first invoke its superclass's designated initializer to initialize inherited state
在类继承时调用任何 Designated Initializer 都是合法的,而且应该保证父类的 Designated Initializer 能够得到调用,这样会使继承关系上的类都得以正确的初始化。
虽然没有这样的明确规定,但 Apple 的框架都遵守这个约定,我们也应该尽力遵守。
当定义一个新类 API 时,有时会有不同的方式:
- 不需要重写任何初始化方法
- 重写 Designated Initializer
- 定义一个新的 Designated Initializer
对于第一种,不需要再做其他操作,只需要规范调用父类 Designated Initializer 方法即可。
对于第二种,需要在重写方法里调用 super 方法以初始化父类参数,然后再进行特定初始化。
对于第三种,确保调用了父类的 Designated Initializer,并且本类的 Secondary Initializer 调用自己的 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
{
// 调用了父类的 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,但是依然会调用父类的 Designated Initializer;
- 子类的 Secondary Initializer 调用了本类的 Designated Initializer。
间接初始化方法
对于间接初始化方法,应尽可能地调用 指定初始化方法,以保证最终调用的是 指定初始化方法。调用时对于一些参数提供默认值或 nil。
小结
- 指定一个 指定初始化方法,以尽可能多的初始化参数,避免出现意外;
- 对于一个类来说,间接初始化方法 的实现应调用 指定初始化方法;
- 对于继承时有三种情况:
-
不重写任何初始化方法
正常调用
-
重写指定初始化方法
调用父类指定初始化方法
-
自定义一个新的初始化方法
调用父类指定初始化方法,并本类遵循“间接初始化方法 的实现应调用 指定初始化方法”
-
参考
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