iOS

第 33 章——init & instancetype

2017-04-19  本文已影响15人  独木舟的木

[TOC]

init

NSObject 类有一个名为 init 的方法。init 的示例代码如下:

NSMutableArray *mutableArray = [[NSMutableArray alloc] init];

向新创建的对象发送 init 消息,它就会初始化其下的示例变量。

覆盖 init 方法

- (instancetype)init {
    // 调用父类 NSObject 的 init 方法
    // 将父类的 init 方法所返回的对象赋给 self
    self = [super init];
  
    // 检查父类的 init 方法的返回值是否非 nil ?
    // 1.出于优化考虑,init 方法会释放已经分配了内存的对象,然后创建另一个新对象并返回;
    // 2.如果 init 方法在执行过程中发生错误,会释放对象并返回 nil
    if (self) {
      
        // 为 _voltage 赋初值
        _voltage = 120;
    }
  
    // 返回指向新对象的指针
    return self;
}

使用并检查父类的初始化方法:覆盖 init 方法时,需要检查父类的初始化方法的返回值,确定不是 nil 并且有效。如果对象不存在,就没有必要执行自定义的初始化方法。

instancetype 类型

instancetype 和 id 的区别

instancetypeid 都可以作为初始化方法的返回值。

详情参考: iOS instancetype和id区别详解

带实参的 init 方法

// 串联(chain)使用初始化方法
// 防止因为调用了父类的 init 方法,导致子类实例的初始化不完整
- (instancetype)init {

    return [self initWithProductName:@"unKnown"];
}

// 指定初始化方法
- (instancetype)initWithProductName:(NSString *)productName {
    // 调用父类 NSObject 的 init 方法
    self = [super init];
    
    // 是否返回非 nil 的值?
    if (self) {
        
        // 为 _productName 赋值
        _productName = [productName copy];
        
        // 为 _voltage 赋初值
        _voltage = 120;
    }
    // 返回指向新对象的指针
    return self;
}

/**
 *  description 方法会返回一个描述类实例的字符串
 *
 *  默认的 NSObject 实现会以字符串的形式返回该对象在内存上的地址
 */
- (NSString *)description {
    return [NSString stringWithFormat:@"%@:%d volts",
            self.productName,self.voltage];
}

在 init 方法中使用存取方法

// 指定初始化方法
- (instancetype)initWithProductName:(NSString *)productName {
    // 调用父类 NSObject 的 init 方法
    self = [super init];
    
    // 是否返回非 nil 的值?
    if (self) {
        
      // 1.直接赋值
        // 为 _productName 赋值
//        _productName = [productName copy];
        // 为 _voltage 赋初值
//        _voltage = 120;
      
        // 2.使用存取方法
        [self setProductName:productName];
        [self setVoltage:120];
    }
    // 返回指向新对象的指针
    return self;
}

争议1:不能在 init 方法中使用存取方法。使用存取方法的前提是对象已经初始化完毕,而对象只有在执行完 init 方法后才算完成了初始化。

有一个例外:永远不要在 init 方法(以及其他初始化方法)里面用 getter 和 setter 方法,你应当直接访问实例变量。这样做是为了防止有子类时,出现这样的情况:它的子类最终重载了其 setter 或者 getter 方法,因此导致该子类去调用其他的方法、访问那些处于不稳定状态,或者称为没有初始化完成的属性或者 ivar 。记住一个对象仅仅在 init 返回的时候,才会被认为是达到了初始化完成的状态。

——禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)

争议2:在实际的开发中,存取方法除了能为实例变量赋值,还会完成其他的任务。

多个初始化方法

BNROwnedAppliance *appliance = [[BNROwnedAppliance alloc] init];

💡 BNROwnedAppliance 没有实现 init 方法,所以会调用其父类 BNRApplianceinit 方法:

- (instancetype)init {
    
    return [self initWithProductName:@"unKnown"];
}

从而调用:[self initWithProductName:@"unKnown"] ,因为 self 指向的是 BNROwnedAppliance 实例,所以调用的是 BNROwnedApplianceinitWithProductName: 方法:

- (instancetype)initWithProductName:(NSString *)productName {
    
    return [self initWithProductName:productName firstOwnerName:nil];
}

而该方法又会调用[self initWithProductName:productName firstOwnerName:nil]

这样以上多个初始化方法串联了起来。

编写初始化方法时,应该遵守以下规则:

禁用 init 方法

// 抛出异常
- (instancetype)init {
    @throw [NSException exceptionWithName:@"Method Undefined"
                                   reason:@"Use +initWithProductName:"
                                 userInfo:nil];
    return nil;
}
 
- (instancetype)init {
    [NSException raise:@"BNRInitialization" format:@"Use +initWithProductName:"];
}
上一篇下一篇

猜你喜欢

热点阅读