美文网首页编写高质量代码的52个有效方法
52个有效方法(16) - 提供全能初始化方法

52个有效方法(16) - 提供全能初始化方法

作者: SkyMing一C | 来源:发表于2018-09-04 11:50 被阅读7次

    我们把可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(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_INITIALIZERNS_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_INITIALIZERNS_UNAVAILABLE都能清晰的告知调用者应该如何调用方法。

      • 如果是可以给出默认值初始化方法,那么使用 NS_DESIGNATED_INITIALIZER 就可以。

      • 如果是必须要用某参数来初始化的,可以使用 NS_UNAVAILABLE

    要点
    1. 在类中提供一个全能初始化方法,并于文档中指明。其他初始化方法均应调用此方法。

    2. 若全能初始化方法与超类不同,则需覆写超类中的对应方法。

    3. 如果超类中的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。

    相关文章

      网友评论

        本文标题:52个有效方法(16) - 提供全能初始化方法

        本文链接:https://www.haomeiwen.com/subject/hjkiwftx.html