在 iOS 里面,不管是 Objective-C 仍是 Swift,类(结构体、枚举)的初始化都有必定的规则要求,只不过在 Objective-C 中会比较宽松,若是不按照规则也不会报错,但会存在隐患,而在 Swift 则须要严格按照规则要求代码才能编译经过,极大提升了代码的安全性。html
类(结构体、枚举)的初始化有两种初始化器(初始化方法):指定初始化器(Designated Initializers )、便利初始化器(Convenience Initializers)swift
指定初始化器是类(结构体、枚举)的主初始化器,类(结构体、枚举)初始化的时候必须调用自身或者父类的指定初始化器。一个类(结构体、枚举)能够有多个指定初始化器,做用是表明从不一样的源进行初始化。一个类(结构体、枚举)除非有多种不一样的源进行初始化,不然不建议建立多个指定初始化器。在 iOS 里,视图控件类,若是:UIView
、UIViewController
就有两个指定初始化器,分别表明从代码初始化、从Nib
初始化安全
便利初始化器是类(结构体、枚举)的次要初始化器,做用是使类(结构体、枚举)在初始化时更方便设置相关的属性(成员变量)。既然便利初始化器是为了便利,那么一个类(结构体、枚举)就能够有多个便利初始化器,这些便利初始化器里面最后都须要调用自身的指定初始化器ide
iOS 的初始化最核心两条的规则:ui
全部的其余规则都根据这两条规则而展开,只是 Objective-C 没有那么多安全检查,显得比较随意、宽松,而 Swift 则有一堆的限制。atom
Objective-C 在初始化时,会自动给每一个属性(成员变量)赋值为 0 或者 nil
,没有强制要求额外为每一个属性(成员变量)赋值,方便的同时也缺乏了代码的安全性。spa
Objective-C 中的指定初始化器会在后面被NS_DESIGNATED_INITIALIZER
修饰,如下为NSObject
和UIView
的指定初始化器code
// 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
或者其余类。cdn
不管继承自什么类,都常常须要新的初始化方法,而这个新的初始化方法其实就是新的指定初始化器。若是存在一个新的指定初始化器,那么原来的指定初始化器就会自动退化成便利初始化器。为了遵循必需要调用指定初始化器的规则,就必须重写旧的定初始化器,在里面调用新的指定初始化器,这样就能确保全部属性(成员变量)被初始化htm
根据这条规则,能够从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
复制代码
固然,一个新的类也能够不增长新的初始化方法,在 Objective-C 中,子类会直接继承父类全部的初始化方法
在 Swift 中,初始化器的规则严格且复杂,目的就是为了使代码更加安全,若是不符合规则,会直接报错,经常会让刚接手 Swift 或者一直对 iOS 的初始化没有深刻理解的人很头疼。其实核心规则仍是同样,只要理解了各个规则的含义和做用,写起来仍是没有压力。
从 iOS 初始化的核心规则展开而来,Swift 多了一些规则:
self
相关的任何东西,例如:调用实例属性,调用实例方法。这种状况处理就十分简单,本身里面的init
方法就是它的指定初始化器,并且能够随意建立多个它的指定初始化器。若是须要建立便利初始化器,则在方法名前面加上convenience
,且在里面必须调用其余初始化器,使得最后确定调用指定初始化器
class Person {
var name: String
var age: Int
// 能够存在多个指定初始化器
init(name: String, age: Int) {
self.name = name;
self.age = age;
}
// 能够存在多个指定初始化器
init(age: Int) {
self.name = "Daniels";
self.age = age;
}
// 便利初始化器
convenience init(name: String) {
// 必需要调用本身的指定初始化器
self.init(name: name, age: 18)
// 必须在初始化完成后才能调用实例方法
jump()
}
func jump() {
}
}
复制代码
若是子类没有新的非可选类型属性,或者保证全部非可选类型属性都已经有默认值,则能够直接继承父类的指定初始化器和便利初始化器
class Student: Person {
var score: Double = 100
}
复制代码
若是子类有新的非可选类型属性,或者没法保证全部非可选类型属性都已经有默认值,则须要新建立一个指定初始化器,或者重写父类的指定初始化器
class Student: Person {
var score: Double
// 新的指定初始化器,若是有新的指定初始化器,就不会继承父类的全部初始化器,除非重写
init(name: String, age: Int, score: Double) {
self.score = score
super.init(name: name, age: age)
}
// 重写父类的指定初始化器,若是不重写,则子类不存在这个方法
override init(name: String, age: Int) {
score = 100
super.init(name: name, age: age)
}
// 便利初始化器
convenience init(name: String) {
// 必需要调用本身的指定初始化器
self.init(name: name, age: 10, score: 100)
}
}
复制代码
须要注意的是,若是子类重写父类全部指定初始化器,则会继承父类的便利初始化器。缘由也是很简单,由于父类的便利初始化器,依赖于本身的指定初始化器
在 Swift 中能够定义一个可失败的初始化器(Failable Initializers),表示在某些状况下会建立实例失败。
只有在表示建立失败的时候才有返回值,而且返回值为nil
。
子类能够把父类的可失败的初始化器重写为不可失败的初始化器,但不能把父类的不可失败的初始化器重写为可失败的初始化器
class Animal {
let name: String
// 可失败的初始化器,若是把 ! 换成 ?,则为隐式的可失败的初始化器
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class Dog: Animal {
override init(name: String) {
if name.isEmpty {
super.init(name: "旺财")!
} else {
super.init(name: name)!
}
}
}
复制代码
在 Swift 中,可使用required
修饰初始化器,来指定子类必须实现该初始化器。须要注意的是,若是子类能够直接继承父类的指定初始化器和便利初始化器,因此也就能够不用额外实现required
修饰的初始化器
子类实现该初始化器时,也必须加上required
修饰符,而不是override
class MyView: UIView {
var name: String
init(frame: CGRect, name: String) {
self.name = name;
super.init(frame: frame)
}
// 必须实现此初始化器,但因为是可失败的初始化器,因此里面能够不作具体实现
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
复制代码
iOS 的初始化最核心两条的规则:
展开而来的多条规则:
required
修饰的初始化器