《Objective-C 编程》33.init & instancetype & id

init

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

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

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

  • alloc 负责分配对象空间, init 负责初始化对象。
  • 如果创建出来的新对象没有经过初始化,它仅仅是存在于内存中,但是无法接收消息。
  • init 是实例方法,返回的是初始化后的对象地址。
  • initNSObject 的初始化方法。
  • 如果 NSObject 的子类没有实现 init 方法,就会默认执行 NSObjectinit 方法,将实例变量初始化为 0。

覆盖 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 关键字会告诉编译器返回什么类型的对象。你编写的或是覆盖的任何初始化方法都应该返回 instancetype 类型的值。

instancetype 和 id 的区别

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

  • instancetype :关联返回类型,使【非关联返回类型的方法】返回【所在类的类型】。
  • id:只能返回未知类型的对象。
  • instancetype 只能作为返回值使用,而 id 既能作为返回值,还能作为参数使用。
  • 相对于 id ,instancetype 还可以让编译器检查返回值的类型。

详情参考: 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:在实际的开发中,存取方法除了能为实例变量赋值,还会完成其他的任务。

多个初始化方法

  • BNRAppliance.h

    #import <Foundation/Foundation.h>
    
    @interface BNRAppliance : NSObject
    
    @property (nonatomic, copy) NSString *productName;
    @property (nonatomic) int voltage;
    - (instancetype)initWithProductName:(NSString *)productName;
    
    @end
    
  • BNRAppliance.m

    #import "BNRAppliance.h"
    
    @implementation BNRAppliance
    
    // 串联(chain)使用初始化方法
    - (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];
    }
    
    @end
    
  • BNROwnedAppliance.h

    #import "BNRAppliance.h"
    
    @interface BNROwnedAppliance : BNRAppliance
    
    // 保存拥有者的姓名
    @property (readonly) NSSet *ownerNames;
    
    - (instancetype)initWithProductName:(NSString *)productName
                     firstOwnerName:(NSString *)firstOwnedName;
    - (void)addOwnerName:(NSString *)name;
    - (void)removeOwnerName:(NSString *)name;
    
    @end
    
  • BNROwnedAppliance.m

    #import "BNROwnedAppliance.h"
    
    @interface BNROwnedAppliance () {
        NSMutableSet *_ownerNames;
    }
    
    @end
    
    @implementation BNROwnedAppliance
    
    - (instancetype)initWithProductName:(NSString *)productName {
        return [self initWithProductName:productName firstOwnerName:nil];
    }
    
    // 指定初始化方法
    - (instancetype)initWithProductName:(NSString *)productName
                         firstOwnerName:(NSString *)firstOwnedName {
        // 调用父类的初始化方法
        if (self = [super initWithProductName:productName]) {
            
            // 创建 NSMutableSet 实例,用于保存拥有者的姓名
            _ownerNames = [[NSMutableSet alloc] init];
            
            // 传入的第一个拥有者姓名是否为 nil ?
            if (firstOwnedName) {
                [_ownerNames addObject:productName];
            }
        }
        // 返回指向新对象的指针
        return self;
    }
    
    - (void)addOwnerName:(NSString *)name {
        [_ownerNames addObject:name];
    }
    
    - (void)removeOwnerName:(NSString *)name {
        [_ownerNames removeObject:name];
    }
    
    - (NSSet *)ownerNames {
        return [_ownerNames copy];
    }
    @end
    
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:"];
}
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,698评论 0 9
  • 在上海虹桥火车站,由于没有二代身份证,且是聪哥哥买的票,需要提前知道订单号,才好在人工取票窗取网络购买的票。 去到...
    mj小鸽子阅读 142评论 0 0
  • 这几天都在武汉玩,昨天晚上感冒了,有点发烧,早上醒来的时候依然在发烧。中午撑着去吃饭,买药,拿快递,剪头发。...
    等我胖了再揍你啊阅读 227评论 0 0
  • 现在依然很多创业者想在网上创业,开网店上架-卖货-收钱。根据去年的统计,淘宝有1000万卖家,但有成交量的大概只有...
    我是酱职阅读 263评论 0 1
  • SZ小甜甜阅读 615评论 0 0