52个有效方法(16) - 提供全能初始化方法
2018-09-04 本文已影响7人
SkyMing一C
我们把可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)。
如果类有多个初始化方法,那么需要在其中选定一个作为“全能初始化方法”,令其他初始化方法都来调用它。
于是,只有在全能初始化方法中,才会存储内部数据。这样的话,当底层数据存储机制改变时,只需要修改此方法的代码就好,无需改动其他初始化方法。
全能初始化方法示例
- 自定义一个表示矩形的类EOCRectangle
@interface EOCRectangle : NSObject
@property (nonatomic,readonly) float width;
@property (nonatomic,readonly) float height;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
@end
@implementation EOCRectangle
//全能初始化方法
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
{
self = [super init];
if (self) {
_width = width;
_height = height;
}
return self;
}
@end
其中- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
这个方法就是全能初始化方法。其他的初始化方法都应该调用这个方法来创建对象。
- 需要复写已经有的初始化方法,比如init方法。这样不管使用者怎么创建都能通过这个全能初始化方法正常创建。
@interface EOCRectangle : NSObject
@property (nonatomic,readonly) float width;
@property (nonatomic,readonly) float height;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
@end
@implementation EOCRectangle
- (instancetype)init{
return [self initWithWidth:500 height:500];
}
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
{
self = [super init];
if (self) {
_width = width;
_height = height;
}
return self;
}
@end
- 类继承时需要注意的一个重要问题:如果子类的全能初始化方法与超类方法的名称不同,那么总应覆写超类的全能初始化方法。
@interface EOCSquare : EOCRectangle
- (instancetype)initWithDimension:(CGFloat)dimension;
@end
@implementation EOCSquare
//如果使用者继续使用父类的全能初始化方法呢,这样就有可能出现宽高不等的正方形。所以还应该阻止使用者直接调用父类的全能初始化方法
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must be use initWithDimension :instead" userInfo:nil];
}
- (instancetype)initWithDimension:(CGFloat)dimension
{
return [super initWithWidth:dimension height:dimension];
}
//如果使用者还是用init来创建,这样还是调用父类中的init方法,还是有可能出现长宽不等的情况,所以还应该复写一下init方法
-(instancetype)init{
return [self initWithDimension:500];
}
@end
- 如果有两种及两种以上的全能初始化方法,则需要覆写每一种全能初始化方法,然后在其中调用父类的全能初始化方法。例如EOCRectangle上添加NSCoding协议。
#import <Foundation/Foundation.h>
@interface EOCRectangle : NSObject <NSCoding>
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
- (id)initWithWidth:(float)width
andHeight:(float)height;
@end
@implementation EOCRectangle
// Designated initialiser
- (id)initWithWidth:(float)width
andHeight:(float)height
{
if ((self = [super init])) {
_width = width;
_height = height;
}
return self;
}
// Super-class’s designated initialiser
- (id)init {
return [self initWithWidth:5.0f andHeight:10.0f];
}
// Initialiser from NSCoding
- (id)initWithCoder:(NSCoder*)decoder {
// Call through to super’s designated initialiser
if ((self = [super init])) {
_width = [decoder decodeFloatForKey:@"width"];
_height = [decoder decodeFloatForKey:@"height"];
}
}
@end
请注意,NSCoding协议的初始化方法没有调用本类的全能初始化方法,而是调用了超类的相关方法。然而,若超类也实现了NSCoding,则需改为调用超类的"initWithCoder:"初始化方法。例如,在此情况下,EOCSquare类就得这么写:
#import "EOCRectangle.h"
@interface EOCSquare : EOCRectangle
- (id)initWithDimension:(float)dimension;
@end
@implementation EOCSquare
// Designated initialiser
- (id)initWithDimension:(float)dimension {
return [super initWithWidth:dimension andHeight:dimension];
}
// Super class designated initialiser
- (id)initWithWidth:(float)width andHeight:(float)height {
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}
// NSCoding designated initialiser
- (id)initWithCoder:(NSCoder*)decoder {
if ((self = [super initWithCoder:decoder])) {
// EOCSquare’s specific initialiser
}
}
@end
每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上,然后再执行与本类有关的任务。
Designated Initializer 指定初始化方法
NS_DESIGNATED_INITIALIZER
与NS_UNAVAILABLE
- 当在接口中指定初始化方法的后面加上宏
NS_DESIGNATED_INITIALIZER
,编译器就会检查我们实现的初始化调用链是否符合规则,并提示相应的警告。另外NS_DESIGNATED_INITIALIZER
也起到了标明指定初始化方法的注释作用。
- (id)initWithDimension:(float)dimension NS_DESIGNATED_INITIALIZER;
-
NS_UNAVAILABLE
的作用是,直接禁用其他初始化方法。方法一旦标记 NS_UNAVAILABLE ,那么在 IDE 自动补全时,就不会索引到该方法,并且如果强制调用该方法,编译器会报错(但并不代表着方法不能被调用,runtime 依然可以做到)。
/**
:nodoc:
*/
- (instancetype)init NS_UNAVAILABLE;//< 直接标记 init 方法不可用
/**
:nodoc:
*/
+ (instancetype)new NS_UNAVAILABLE;
-
NS_DESIGNATED_INITIALIZER
与NS_UNAVAILABLE
都能清晰的告知调用者应该如何调用方法。-
如果是可以给出默认值初始化方法,那么使用
NS_DESIGNATED_INITIALIZER
就可以。 -
如果是必须要用某参数来初始化的,可以使用
NS_UNAVAILABLE
。
-
要点
-
在类中提供一个全能初始化方法,并于文档中指明。其他初始化方法均应调用此方法。
-
若全能初始化方法与超类不同,则需覆写超类中的对应方法。
-
如果超类中的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。