oc——类——初始化

概述

iOS 中对象创建是分两步完成:
  • 分配内存
  • 初始化对象数据成员
创建NSObject对象的过程:
oc——类——初始化_第1张图片
苹果官方有一副图片更生动的描述了这个过程:
oc——类——初始化_第2张图片

初始化方法

c++类有构造函数,构造函数负责实例对象初始化,构造函数函数名与类名一致,易识别,且会自动调用
oc类没有构造函数,但有类似构造函数的初始化方法,初始化函数与类名不一致,且不会自动调用
oc类初始化方法特征:
  • instance method
  • Method返回类型为instancetype
  • Method的SEL以init起始
初始化方法分类:
  • 指定初始化方法:designated initializer,以NS_DESIGNATED_INITIALIZER标记
  • 便利初始化方法:convenience initializer,无NS_DESIGNATED_INITIALIZER标记
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
__has_attribute是Clang的一个用于检测当前编译器是否支持某一特性的宏,通过上面定义,可以知道NS_DESIGNATED_INITIALIZER其实是给初始化方法声明的后面加上了一个编译器可见的标记,通过这个标记,可以在编译时就帮开发者找出一些潜在的初始化问题,避免程序运行时出现一些奇怪行为,编译器会通过warning提示开发者潜在的初始化问题
指定初始化方法对一个类来说非常重要,指定初始化方法一般分两种极端情况:
  • 对每个数据成员提供参数,这样一来,指定初始化方法就有一大堆参数,优点是提供极大的灵活性,可自由指定每个数据成员值,缺点是试想每次开发者需要创建实例对象就需要提供一堆参数,初始化极不方便且易错误
  • 不提供参数,每个数据成员都在初始化方法中指定默认值,优点是初始化及其方便且不易错误,缺点是缺乏灵活性,无法自由指定任一数据成员值
上述两种极端的指定初始化方法都有缺点,便利初始化方法就是用来解决上述两种指定初始化方法缺点的,便利初始化方法提供特定参数,用来对特定数据成员设置特定值,在便利初始化方法中调用指定初始化方法,用特定参数对特定数据成员设置特定值,其他数据成员使用固定值或默认值,便利初始化方法让开发者比较简单方便的创建对象,因此可把指定初始化方法理解为通用初始化方法,对所有数据成员提供特定值或默认值,便利初始化方法理解为特定初始化方法,对特定数据成员设置特定值,其他数据成员使用固定值或默认值
c++类构造函数的设计原则确保类的所有成员都得到合适的初始化,oc类初始化方法设计原则亦如此,确保类的所有数据成员得到合适的初始化

应用

@interface FBAnimal : NSObject
{
    NSString *_name;
}

- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;

@end

@implementation FBAnimal

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if(self)
    {
        _name = name;
    }
    
    return self;
}

- (instancetype)init
{
    return [self initWithName:@"FBAnimal"];
}

@end

@interface FBMammal : FBAnimal
{
    NSInteger _numberOfLegs;
}

- (instancetype)initWithName:(NSString *)name andLegs:(NSInteger)numberOfLegs NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithLegs:(NSInteger)numberOfLegs;

@end

@implementation FBMammal

- (instancetype)initWithName:(NSString *)name andLegs:(NSInteger)numberOfLegs
{
    self = [super initWithName:name];
    if(self)
    {
        _numberOfLegs = numberOfLegs;
    }
    
    return self;
}

- (instancetype)initWithName:(NSString *)name
{
    return [self initWithName:name andLegs:4];
}

- (instancetype)initWithLegs:(NSInteger)numberOfLegs
{
    self = [self initWithName:@"FBMammal"];
    if(self)
    {
        _numberOfLegs = numberOfLegs;
    }
    
    return self;
}

@end

@interface FBWhale : FBMammal
{
    BOOL _canSwim;
}

- (instancetype)initWhale NS_DESIGNATED_INITIALIZER;

@end

@implementation FBWhale

- (instancetype)initWhale
{
    self = [super initWithName:@"Whale" andLegs:0];
    if(self)
    {
        _canSwim = YES;
    }
    
    return self;
}

- (instancetype)initWithName:(NSString *)name andLegs:(NSInteger)numberOfLegs
{
    return [self initWhale];
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"Name: %@, Numberof Legs %ld, CanSwim %@", _name, _numberOfLegs, _canSwim ? @"YES" : @"NO"];
}

@end
- (void)use_init
{
    FBWhale *whale1 = [[FBWhale alloc] initWhale];
    NSLog(@"whale1 %@", whale1);
    
    FBWhale *whale2 = [[FBWhale alloc] initWithName:@"Whale"];
    NSLog(@"whale2 %@", whale2);
    
    FBWhale *whale3 = [[FBWhale alloc] init];
    NSLog(@"whale3 %@", whale3);
    
    FBWhale *whale4 = [[FBWhale alloc] initWithLegs:4];
    NSLog(@"whale4 %@", whale4);
    
    FBWhale *whale5 = [[FBWhale alloc] initWithName:@"Whale" andLegs:8];
    NSLog(@"whale5 %@", whale5);
}
output:
whale1 Name: Whale, Numberof Legs 0, CanSwim YES
whale2 Name: Whale, Numberof Legs 0, CanSwim YES
whale3 Name: Whale, Numberof Legs 0, CanSwim YES
whale4 Name: Whale, Numberof Legs 4, CanSwim YES
whale5 Name: Whale, Numberof Legs 0, CanSwim YES

总结

oc——类——初始化_第3张图片
oc类初始化方法设计原则确保类的所有数据成员都能得到合适的初始化,因此类初始化方法设计遵守下面规范:
  • 如果类有指定初始化方法,那么便利初始化方法必须直接或间接调用自身类的指定初始化方法,不能调用super的初始化方法
  • 子类如果有指定初始化方法,那么指定初始化方法实现时必须调用它的直接父类的指定初始化方法
  • 如果子类提供了指定初始化方法,那么父类的指定初始化方法就“退化”为子类的便利初始化方法,因此子类要override父类的所有指定初始化方法,如上述FBAnimal的init便利初始化方法override了NSObject的init指定初始化方法,FBMammal的initWithName便利初始化方法override了FBAnimal的initWithName指定初始化方法,FBWhale的initWithName:andLegs便利初始化方法override了FBMammal的initWithName:andLegs指定初始化方法
遵守上述oc类初始化方法设计规范之后,就可确保类的所有数据成员都能得到合适的初始化,初始化分以下几种情况:
  • case 1:调用指定初始化方法,指定初始化方法初始化了类的所有数据成员,因为指定初始化递归调用父类指定初始化方法,初始化路线为case 1
  • case 2:调用便利初始化方法,便利初始化方法直接或间接调用指定初始化方法,初始化路线为case 2->case 1
  • case 3:调用父类指定初始化方法,子类override了父类指定初始化方法,因此实质调用的是子类便利初始化方法,回归到case 2,初始化路线为case 2->case 1
  • case 4:调用父类便利初始化方法,父类便利初始化方法调用父类指定初始化方法,回归到case 3,初始化路线为case 2->case 1
上述分析可见oc类初始化方法核心是指定初始化方法,因为最终都调用到指定初始化方法,因此指定初始化方法必须调用父类指定初始化方法,并且初始化自身数据成员,这样就可确保类的所有数据成员都能得到合适的初始化

你可能感兴趣的:(oc)