Swift的初始化方法是为 类、结构体、枚举使用的,它能为每个存储属性
提供初始值,Swift的初始化方法和OC的初始化方法不同,Swift的初始化方法不需要返回一个值(对象)。它的主要任务是在该类的实例对象使用前充分地初始化该对象。
当然了,类对象一样可以实现初始化方法。
1、初始化方法的一般形式
init(参数标签 参数名 : 参数类型) {
//init code
}
2、初始化方法必须要为每一个
存储属性提供初始值
2.1 下面的Vehicle
类声明了两个存储属性:wheelCounts和color,我们在构造初始化方法的时候,必须要初始化这两个存储属性;如果缺少任何一个存储属性的初始化,都会报错!
class Vehicle : NSObject {
var wheelCounts : Int
var color : UIColor
init(wheelCounts wheels : Int, vehicleColor color : UIColor) {
self.wheelCounts = wheels
self.color = color
}
}
//调用
var vehicle = Vehicle.init(wheelCounts: 2, vehicleColor: UIColor.white)
2.2 我们为Vehicle
类增加一个可选型属性price
,其它代码不变,由于可选型默认是被初始化为nil的,所以我们不需要特意初始化可选型属性;
class Vehicle : NSObject {
var wheelCounts : Int
var color : UIColor
var price : Double?
init(wheelCounts wheels : Int, vehicleColor color : UIColor) {
self.wheelCounts = wheels
self.color = color
}
}
//调用
var vehicle = Vehicle.init(wheelCounts: 2, vehicleColor: UIColor.white)
3、值类型(枚举、结构体)初始化委托
初始化委托:一个初始化方法可以调用其它初始化方法来实现属性的初始化;
这样做的好处是:避免重复的初始化代码
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
//调用第2种初始化方法
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
上面有3种初始化方法,init()
,init(origin: Point, size: Size)
以及init(center: Point, size: Size)
;
显然,init(center: Point, size: Size)
是通过调用init(origin: Point, size: Size)
来实现初始化的,这就是初始化委托
4、类的继承和初始化
4.1 特定初始化和便利初始化
特定初始化方法是一个类的主要初始化方法,特定初始化方法能够初始化该类引入的所有属性,并且调用父类的初始化方法,使得初始化链能够继续像上(父类)进行;
每个类至少要有一个特定初始化方法,某些情况下,可以从父类继承一个或多个初始化方法;
便利初始化方法是次要的,你可以定义一个便利初始化方法,在这个方法里,必须要调用本类的某个特定初始化方法;在调用本类的特定初始化方法的过程中,既可以使得初始化链继续向父类进行,又能给那些在特定初始化方法中初始化的属性一个默认值,这样我们就不需要再写代码对这部分属性进行赋值了,可以减少重复代码。
当然了,遍历初始化方法并不是必要的。
4.2 初始化方法的语法
特定初始化方法的语法
init(parameters) {
statements
}
遍历初始化方法的语法
convenience init(parameters) {
statements
}
4.3 类的初始化代理
为了简化特定初始化方法和便利初始化方法的关系,Swift声明了3条规则
一个类的 特定初始化方法 必须要调用其父类的 特定初始化方法
便利初始化方法必须要调用该类的另一个始化方法
便利初始化方法的最后必须要调用该类的某一个
特定初始化方法
解读:
规则1是为了使得子类的初始化方法能够向上委托,能实现其父类甚至超类的属性初始化;
规则2表示便利初始化方法内必须要调用该类的另一个初始化方法(可以是特定初始化方法,也可以是便利初始化方法);
规则3则是说便利初始化方法的最后必须要调用一个特定初始化方法
,这和规则2看起来有点冲突?
其实并不冲突,规则2要求每个便利初始化方法都要调用该类的另一个初始化方法,即使它调用的是另一个便利初始化方法,但是这个被调用的便利初始化方法肯定还是会间接或者直接调用某一个特定初始化方法
的;
简单来说就是:
- 特定初始化方法是向上委托
-
便利初始化方法总是横向委托
上图中的父类有1个特定初始化方法和2个便利初始化方法,便利初始化方法1调用了特定初始化方法,另一个便利初始化方法,调用了便利初始化方法1,最终,还是调用了特定初始化方法。
子类则包含2个特定初始化方法和1个便利初始化方法,2个特定初始化方法都是继承自父类唯一的特定初始化方法的;子类的便利初始化方法则还是横向调用本类的特定初始化方法,而不是直接继承自父类的便利初始化方法。
4.4 2段初始化
Swift中的类的初始化有2个阶段
阶段1:每个存储属性必须要被初始化且分配值
阶段2:每个类的实例对象在被使用之前都有机会进一步定制其存储属性
这两个阶段为的是防止属性在初始化之前就被访问了,也可以防止属性值被另一个初始化程序意外地设置为不同的值。
Swift编译器执行了4次有用的安全检查,以确保两阶段初始化安全无误。
1、在特定初始化方法中,该类引入的所有属性被初始化之后,才能调用父类的初始化方法
原因:一个类的实例对象,只有当它的所有属性被初始化之后,才能认为该对象被完全初始化;所以在子类中引入的属性必须要先被初始化赋值,然后才能使初始化链向上委托(调用super.init(***) )
2、在特定初始化方法中,必须要先调用父类的初始化方法,然后才能对继承来的属性进行赋值
原因:如果先对继承来的属性进行初始化赋值,再调用父类的初始化方法,可能会改变从父类继承来的属性的值
3、在便利初始化方法中,必须要先调用该类的另一个初始化方法,然后再为其属性赋值
原因:如果我们先对属性进行初始化赋值,再调用另一个初始化方法,有可能会导致已经被初始化的属性的值被修改了
4、任何一个初始化方法中,必须要先完成第一阶段的初始化工作,才能使用self关键字
、调用实例方法、读取属性的值。
下面说明了两个阶段的工作已经完成的标志
阶段1:
- 一个特定初始化方法或者便利初始化方法被调用
- 类的实例对象的内存已经被分配,但是还没被初始化
- 该类的一个特定初始化方法保证该类引入的存储属性都有值了,这些存储属性就被初始化了
- 特定初始化方法向上委托,调用父类的初始化方法,以确保父类引入的存储属性被初始化赋值
- 继续向上委托,直到继承链的顶端
- 直到继承链的顶端,且该继承链最上面的类确保它引入的存储属性都被初始化了,则该实例对象的的内存完全被初始化了,阶段1结束
阶段2:
- 从继承链的顶部向下,每一个类的特定初始化方法都能够进一步自定义这个实例对象。现在可在初始化方法中访问
self
、修改它的属性、调用它的实例方法等 - 最终,继承链里的每个便利初始化方法都能自定义实例对象和使用
self关键字
下面展示一下阶段1
在这个例子中,从调用子类的便利初始化方法开始,便利输入法横向委托,,调用本类的特定初始化方法。
这个特定初始化方法确保子类引入的存储属性都被初始化赋值了,然后向上委托,调用父类的特定初始化方法。
父类的特定初始化方法确保父类引入的所有存储属性被初始化赋值了;这里没有更上一层的父类了,到此为止;
当父类所有的属性都有值了,第一阶段就完成了。
下面展示一下阶段2
首先,阶段2是从上往下的。父类的特定初始化方法有机会去自定义实例对象;当父类的特定初始化方法结束了,子类的特定初始化方法可以去自定义对象;
最终,一旦子类的特定初始化方法结束了,便利初始化方法就可以开始自定义。
5、初始化方法的继承和重写
Swift并不会像OC那样自动继承父类的初始化方法,因为如果父类的一个较为简单的初始化方法被更加复杂多变的子类自动继承了,有可能会导致子类的实例对象初始化不完全。
但是有些时候,子类是会自动继承父类的初始化方法的。
在给子类写初始化方法时,如果匹配了父类的某个 特定初始化方法,那就要在子类的该初始化方法前加上修饰词override
,即便是重写默认自动生成的初始化方法时也需要加上override
修饰词。
就像重写属性、方法或者下标,override
关键词提示Swift去检查父类是否有匹配的特定初始化方法,并且验证被重写的初始化方法的参数是否像预期的一样被指定好。
在子类中,不管我们是直接重写父类的特定初始化方法,还是子类的便利初始化方法重写父类的特定初始化方法,我们都需要使用
override
关键字
相反的,如果你在子类中重写的初始化方法匹配到的是父类的便利初始化方法,则父类的便利初始化方法永远不会被调用,因为我们知道,父类的便利初始化方法是无法被重写的,只有特定初始化方法才能被重写。
看个例子,我们在这里声明了一个Vehicle类,它包含一个自定义的特定初始化方法和一个便利初始化方法
class Vehicle {
var wheelCounts : Int
var color : UIColor
var price : Double?
//自定义的特定初始化方法
init(wheelCounts wheels : Int) {
self.wheelCounts = wheels
self.color = UIColor.white
}
//便利初始化方法
convenience init(color : UIColor) {
self.init(wheelCounts : 0)
self.color = color
}
}
再定义一个Car类,继承自上面的Vehicle类
我们发现,子类定义的便利初始化方法是可以匹配到父类的特定初始化方法的,但是在该便利初始化方法中,还是必须要调用本类的特定初始化方法;这么一看,似乎子类的便利初始化方法和父类的特定初始化方法没什么关系,只是方法名和参数相同而已;
但是如果子类的初始化方法匹配到的是父类的便利初始化方法,那么就会报错:
子类的初始化方法没有重写父类的特定初始化方法。
现在我们可以整理一下上面的所有信息
在子类的特定初始化方法中,要先初始化子类新引入的存储属性,然后调用父类的某个特定初始化方法
(不能是父类的便利初始化方法),最后才能修改从父类继承下来的属性的值。
init(参数名 : 参数类型) {
初始化本类新引入的存储属性
super.init(参数1, 参数2) //调用父类的某个特定初始化方法
修改从父类继承来的属性的值
}
- 子类的特定初始化方法必须要调用父类的特定初始化方法,不可以调用父类的便利初始化方法
- 子类可以重写父类的特定初始化方法,不能重写父类的便利初始化方法
- 子类的便利初始化方法,必须要调用本类的特定初始化方法
- 子类的便利初始化方法可以重写(或者说匹配)父类的特定初始化方法,但是依然需要调用本类的某个特定初始化方法
6、可失败的初始化方法
有时候我们需要定义一个可失败的类、结构体或者枚举的初始化方法;失败的情况可能由无效的初始化参数值、缺少所需的外部资源或者一些其它阻止初始化成功的情况触发。
比如:人的头发数量比如大于等于一,如果传进来的参数是负值,那就可以触发初始化失败。
1、可以定义多个可失败的初始化方法
2、不能同时定义可失败的初始化方法和不可失败的初始化方法
可失败的初始化方法创造了一个初始化对象的可选型,也就是说这个正在初始化的对象是可以为nil的,所以,当我们需要触发失败的时候,我们可以通过return nil
来触发。
严格来说,初始化方法不需要返回值,初始化方法是为了让初始化对象能够完全地、正确无误地完成初始化。所以即使你返回了一个nil来触发失败,这并不表明初始化成功了。
语法:
init?(参数名 :参数类型) {
if(条件) {return nil}
//条件通过执行下面的代码,即表示可继续初始化
//code...
}
例子:
7、枚举的可失败初始化方法
//枚举的可失败初始化方法
enum Fruits {
case Banana
case Oriange
init?(by fruitName : String) {
switch fruitName {
case "banana" :
self = .Banana
case "oriange" :
self = .Oriange
default:
return nil
}
}
}
var fruit = Fruits.init(by: "Apple")
if fruit == nil {
print("Initialize fruit failed!")
}
或者如果枚举有rawValue
,也可以根据rawValue
字段判断是否能够初始化成功
enum Foods : Character {
case cake = "c"
case meat = "m"
}
let food = Foods.init(rawValue: "a")
if food == nil {
print("Initialize food failed!")
}
7.2 可失败初始化方法的重写
可以在子类用不可失败初始化方法来重写父类的可失败初始化方法
class Document {
var name: String?
init() {}
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
子类用一个不可失败的初始化方法重写了父类的init?(name: String)
方法
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
8、必须要被继承的初始化方法
如果我们要求每一个子类都要重写父类的某个初始化方法,可以在该初始化方法前加上关键词required
;同时,子类重写该方法时,必须要在初始化方法前加上required
关键词,不需要写override
关键词
class SomeClass {
required init() {
// initializer implementation goes here
}
}
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
另外,如果程序员不构造初始化方法,结构体也有自动生成的初始化方法