初始化

440 阅读5分钟

Load 和 initialize

+ (void)load

  1. 系统中的每个类(class)及分类(category)被加载到runtime的时候就会运行,且只会调用一次。如果分类和其所属的类都调用了load方法,则先调用类里面的,再调用分类里的。

  2. load方法并不遵从继承规则。即如果某个类本身没有load方法,那么不管其超类是否实现load方法,系统都不会调用

+ (void)initialize

  1. 对于系统中的每个类来说,该方法会在程序首次使用该类前调用,且只调用一次。它是由运行期系统来调用的,绝不应该通过代码直接调用。

  2. 和与其他消息一样,如果某个类未实现它,而其超类实现了,就会运行超类的实现代码。

注意:

load 在main.m之前~会根据Compile Sources中的顺序来加载

父类的+initialize()可能会被多次调用

初始化

类(结构体、枚举)的初始化有两种初始化器(初始化方法):

  • 指定初始化器(Designated Initializers )

  • 便利初始化器(Convenience Initializers)

Designated Initializers

指定初始化器是类(结构体、枚举)的主初始化器,类(结构体、枚举)初始化的时候必须调用自身或者父类的指定初始化器。一个类(结构体、枚举)可以有多个指定初始化器,作用是代表从不同的源进行初始化。一个类(结构体、枚举)除非有多种不同的源进行初始化,否则不建议创建多个指定初始化器。在 iOS 里,视图控件类,如:UIView、UIViewController就有两个指定初始化器,分别代表从代码初始化、从Nib初始化

Convenience Initializers

便利初始化器是类(结构体、枚举)的次要初始化器,作用是使类(结构体、枚举)在初始化时更方便设置相关的属性(成员变量)。既然便利初始化器是为了便利,那么一个类(结构体、枚举)就可以有多个便利初始化器,这些便利初始化器里面最后都需要调用自身的指定初始化器

iOS 初始化核心规则

  1. 必须至少有一个指定初始化器,在指定初始化器里保证所有非可选类型属性都得到正确的初始化(有值)
  2. 便利初始化器必须调用其他初始化器,使得最后肯定会调用指定初始化器

iShot2021-05-06 10.38.32.png

Objective-C

Objective-C 在初始化时,会自动给每个属性(成员变量)赋值为 0 或者 nil,没有强制要求额外为每个属性(成员变量)赋值,方便的同时也缺少了代码的安全性。

Objective-C 中的指定初始化器会在后面被NS_DESIGNATED_INITIALIZER修饰,以下为NSObject 和UIView的指定初始化器

// NSObject
@interface NSObject <NSObject> 

- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;
@end

// UIView
@interface UIView : UIResponder

- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

@end

在 Objective-C 里面,所有类都继承自NSObject。当自定义一个类的时候,要么直接继承自NSObject,要么继承自UIView或者其他类。

无论继承自什么类,都经常需要新的初始化方法,而这个新的初始化方法其实就是新的指定初始化器。 如果存在一个新的指定初始化器,那么原来的指定初始化器就会自动退化成便利初始化器。为了遵循必须要调用指定初始化器的规则,就必须重写旧的定初始化器,在里面调用新的指定初始化器,这样就能确保所有属性(成员变量)被初始化

根据这条规则,可以从NSObject、UIView中看出,由于UIView拥有新的指定初始化器-initWithFrame:,导致父类NSObject的指定初始化器-init退化成便利初始化器。所以当调用[[UIView alloc] init]时,-init里面必然调用了-initWithFrame:

当存在一个新的指定初始化器的时候,推荐在方法名后面加上NS_DESIGNATED_INITIALIZER,主动告诉编译器有一个新的指定初始化器,这样就可以使用 Xcode 自带的Analysis功能分析,找出初始化过程中可能存在的漏洞

@interface MyView : UIView

@property (nonatomic, strong) NSString *name;

// 推荐加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;

@end

@implementation MyView

// 初始化时加入参数name,这个方法已经成为新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
    if (self = [super initWithFrame:frame]) {
        self.name = name;
    }
    return self;
}

// 旧的指定初始化器就自动退化成便利初始化器,必须在里面调用新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame {
    return [self initWithFrame:frame name:@"Daniels"];
}

// 旧的指定初始化器就自动退化成便利初始化器,必须在里面调用新的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder {
    // 这里的实现是伪代码,只是为了满足规则
    return [self initWithFrame:CGRectNull name:@"Daniels"];
}

@end

如果不想去重写旧的指定初始化器,但又不想存在漏洞和隐患,那么可以使用NS_UNAVAILABLE把旧的指定初始化器都废弃,外界就无法调用旧的指定初始化器

@interface MyView : UIView

@property (nonatomic, strong) NSString *name;

// 推荐加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;

// 废弃旧的指定初始化器
- (instancetype)init NS_UNAVAILABLE;

// 废弃旧的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;

// 废弃旧的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;

@end

@implementation MyView

// 初始化时加入参数name,这个方法已经成为新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
    if (self = [super initWithFrame:frame]) {
        self.name = name;
    }

    return self;
}

@end

Case Study

//MessagePromptView.h

@interface MessagePromptView : UIView

- (instancetype)initWithMessage:(NSString *)message NS_DESIGNATED_INITIALIZER;

@end

//MessagePromptView.m

@interface MessagePromptView ()

@property (nonatomic, copy) NSString *message;

@end

@implementation MessagePromptView

- (instancetype)initWithMessage:(NSString *)message{
    if (self = [super initWithFrame:CGRectZero]) {
        _message = message;
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame{
    return [self initWithMessage:nil];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    return [self initWithMessage:nil];
}

@end

640.webp