十三、初始化 Initialization
1. 概述
通过定义构造器 initializers 创建类、结构体和枚举的一个实例。与Objective-C的 initializers 不同的是,Swift中的 initializers 没有返回值。
类类型 class types 的实例也可以实现析构器 deinitializer,在类被释放之前,进行清理工作。
2. stored properties的初始化(Setting Initial Values for Stored Properties)
类和结构体的 stored properties 在他们的实例被创建的时候,就必须有合适的值。
你可以在构造器中初始化他们,也可以在定义的时候给他们设置默认值。
注意:当给 stored property 初始化的时候,不论是使用默认值初始化,还是调用构造器初始化,都不会调用属性监视器。
2.1 构造器 Initializers
构造器是使用 init
关键字定义的没有参数的方法,用来创建某种类型的实例:
init() { // perform some initialization here }
如:
struct Fahrenheit { //华氏温度 var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() println("The default temperature is \(f.temperature)° Fahrenheit") // prints "The default temperature is 32.0° Fahrenheit"
2.2 默认参数值 Default Property Values
当某个参数总是有某个相同的初始化值时,使用默认参数值比在构造器中初始化它要好。当然他们的结果是一样
struct Fahrenheit { var temperature = 32.0 }
3. 自定义初始化方法 Customizing Initialization
下面的例子定义了结构体Celsius
的两个初始化器,用于从不同的温度进行初始化:
struct Celsius { //摄氏度 var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { //由华氏温度得到摄氏温度 temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double) { //由kelvin温度得到摄氏温度 temperatureInCelsius = kelvin - 273.15 } } let boilingPointOfWater = Celsius(fromFahrenheit: 212.0) // boilingPointOfWater.temperatureInCelsius is 100.0 let freezingPointOfWater = Celsius(fromKelvin: 273.15) // freezingPointOfWater.temperatureInCelsius is 0.0
4. 内部和外部参数名 Local and External Parameter Names
如果你没有给 init 方法外部参数名,编译器会自动给构造器添加外部参数名,这个外部参数名与内部参数名同名。
struct Color { let red, green, blue: Double init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } init(white: Double) { red = white green = white blue = white } } let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) let halfGray = Color(white: 0.5)
不写外部参数名将导致编译错误:
let veryGreen = Color(0.0, 1.0, 0.0) // this reports a compile-time error - external names are required
5. 没有外部参数名的构造器参数 Initializer Parameters Without External Names
如果你不希望构造函数使用外部参数名,使用下划线(_
)覆盖编译器默认行为。
struct Celsius { var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double) { temperatureInCelsius = kelvin - 273.15 } init(_ celsius: Double) {//没有外部参数
temperatureInCelsius = celsius } }let bodyTemperature = Celsius(37.0) //37.0
6. 可选值属性 Optional Property Types
当属性为可选值类型 optional type 时,它会被自动初始化为 nil ,表明它在初始化期间还没有值。
class SurveyQuestion {
var text: String
var response: String? //automatically assigned a default value of nil
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
7. 在初始化期间更改Constant Properties (Modifying Constant Properties During Initialization)
你可以在初始化期间更改 Constant Properties 的值,只要你在初始化完毕后能给它一个确切的值。
对类的实例来说,Constant Properties 只能在初始化时被改变。并且Constant Properties 不能被它的子类改变。
class SurveyQuestion { let text: String var response: String? init(text: String) { self.text = text } func ask() { println(text) } } let beetsQuestion = SurveyQuestion(text: "How about beets?") beetsQuestion.ask() // prints "How about beets?" beetsQuestion.response = "I also like beets. (But not with cheese.)"
上面的代码中,text 为constant,那么它只能在init方法中被更改,一旦创建了一个SurveyQuestion 的实例,那么text 就再也不能更改了。
8. 默认构造器 Default Initializers
8.1 基类的默认构造器
Swift为下面的类型提供默认构造器:
- 1)所有的结构体。
- 2)本身没有提供任何构造器的基类。
默认构造器只是创建给所有的属性提供默认值的实例。
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem()
8.2 成员逐一初始化的结构体默认构造器 Memberwise Initializers for Structure Types
如果你没有为结构体提供自定义的构造器话,编译器将为结构体提供一个成员逐个初始化的构造器 memberwise initializer 。
struct Size { var width = 0.0, height = 0.0 } let twoByTwo = Size(width: 2.0, height: 2.0)
如果你想让默认构造器和自定义构造器并存,可以将自定义构造器写在扩展Extention中,后面会介绍Extention。
9. 值类型的构造器代理
一个构造器能够调用其他构造器去实例化自己的部分内容,这就是构造器代理。构造器代理可以避免代码的重复冗余。
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) self.init(origin: Point(x: originX, y: originY), size: size) } }
结构体Rect的第一个构造器没有参数,函数体为空,它与默认构造器相同,返回属性的默认初始值。
let basicRect = Rect() // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
第二个构造器与 memberwise initializer 相同——如果没有自定义构造器的话。
let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) // originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
10. 类的初始化 Class Inheritance and Initialization
Swift 提供两种构造器:designated initializers 和 convenience initializers。
10.1 designated initializers
- 1)designated initializers 是类的第一构造器(primary initializers),它初始化一个类引入的所有属性,并且向上调用父类构造器初始化整个继承链条。
- 2)类至少要有一个 designated initializers。大部分情况下类只有一个 designated initializers,子类可以继承父类的 designated initializers。
10.2 convenience initializers
- 1)convenience initializers 是第二构造器(secondary initializers),它调用 designated initializers 对类进行初始化。
- 2)类可以没有 designated initializers。
- 3)designated initializers 是类的构造器的速记方法。
11. 两种构造器的语法
1)designated initializer
2)convenience initializers
12. 类的构造器代理 Initializer Delegation for Class Types
12.1 两种构造器必须满足以下关系:
- 1)designated 构造器必须调用它的直接父类的 designated 构造器
- 2)convenience 构造器必须调用同一个类中的其他构造器。
- 3)convenience 构造器最终必须调用designated 构造器。
可以按以下方式记忆:
- designated 构造器总是向上代理。(Designated initializers must always delegate up.)
- convenience 构造器总是横向代理。(Convenience initializers must always delegate across.)
如图所示:
Superclass 有一个 Designated构造器和两个 Convenience构造器。一个Convenience构造器调用另一个Convenience构造器,最终调用Designated构造器。
Subclass 有两个 Designated构造器和一个 Convenience构造器,Convenience构造器必须调用其中的一个Designated构造器,因为它必须调用同一个类中的其他构造器。所有的Designated构造器都必须调用父类 Superclass的 Designated构造器。
下图展示了一种更复杂的情况:
12.2 两阶段初始化 Two-Phase Initialization
Swift中类的初始化分两阶段进行:
- 1)第一阶段,所有的 Stored Property 被引进它的类赋予一个初始化值,一旦每个Stored Property 的初始化值确定了以后,第二阶段初始化就开始了。
- 2)第二阶段,每个类都有机会对 Stored Property 进行进一步的定制。
两阶段初始化使得类的初始化更加安全,防止属性在值确定之前被访问,并且防止被其他构造器意外赋值。同时使得初始化具有灵活性。
为了不使初始化出错,Swift编译器进行以下安全检查:
- 1)安全检查1:Designated构造器必须保证,它所在类引入的属性都已经被初始化了,然后才能向上代理调用父类的构造器。因为,只有当一个对象的所有Stored属性都是初始化了的时候,它的存储空间才是被初始化了的(可用)。
- 2)安全检查2:Designated构造器必须先向上代理调用父类的构造器,然后才能给继承来的属性赋值。否则,你的赋值将被父类构造器覆盖。
- 3)安全检查3:Convenience构造器必须先代理调用其他构造器,然后再给其他属性(自己的和继承来的)辅助。否则,你的赋值将被这个类的Designated构造器覆盖。
- 4)安全检查4:只有第一阶段初始化完毕后,构造器才能访问实例的方法和属性,才能使用 self 关键字。
初始化自己的属性 --> 调用父类构造器 --> 给继承来的属性赋值
基于上面的检查,两阶段初始化如下工作:
1)第一阶段:
- 调用 Designated构造器或 Convenience构造器。
- 给新实例分配内存,内存还未初始化。
- Designated构造器确保这个类引入的所有Stored属性有值,即存储这些Stored属性的内存被初始化。
- Convenience构造器调用父类的构造器,确保父类的Stored属性有值。
- 继续进行上一步,直到达到继承链条的最顶层(基类)。
- 一旦达到了继承链条的最顶层,最后的类确保它的所有Stored属性有值。这时这个实例的内存空间就被完全初始化了,第一阶段完成。
2)第二阶段:
从继承链条的最顶层往下,每一个 Designated构造器都有机会进一步定制实例。这时构造器可以使用 self 、可以修改属性、可以调用实例方法等等。
最后,继承链条中的所有的 Convenience构造器都有机会定制实例,并且使用 self。
下图展示了在假定的子类和父类之间构造的阶段1:
在这个例子中,构造过程从对子类中一个Convenience构造器的调用开始。这个Convenience构造器此时没法修改任何属性,它把构造任务代理给同一类中的Designated构造器。如安全检查1所示,Designated构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着造器链一直往上完成父类的构建过程。父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要构建,也就无需继续向上做构建代理。一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,而阶段1也已完成。
以下展示了相同构造过程的阶段2:
父类中的Designated构造器现在有机会进一步来定制实例(尽管它没有这种必要)。一旦父类中的Designated构造器完成调用,子类的Designated构造器可以执行其他的定制操作(同样,它也没有这种必要)。最终,一旦子类的Designated构造器完成调用,最开始被调用的Convenience构造器可以执行其他的定制操作。
12.3 构造器的继承和覆盖 Initializer Inheritance and Overriding
与Objective-C不同,Swift的子类默认不继承父类的构造器。Swift的这种设定防止了在某些情况下,父类中一个简单的构造器被更专业的子类继承,或者使用父类中一个简单的构造器不完全、不正确的创建子类的实例。
在子类中实现新的构造器版本,使用override
关键字覆盖父类构造器。
下面定义了一个基类 Vehicle:
class Vehicle { var numberOfWheels = 0 //Stored Property var description: String { //Computed Property return "\(numberOfWheels) wheel(s)" } }
因为Vehicle是基类,并且没有实现任何自定义的构造器,所以编译器会产生一个默认的构造器,它总是Designated类型构造器,它可以用来创建numberOfWheels为0的实例。
let vehicle = Vehicle() println("Vehicle: \(vehicle.description)") // Vehicle: 0 wheel(s)
下面定义了一个Vehicle的子类:
class Bicycle: Vehicle { override init() {//覆盖父类构造函数 super.init() //先初始化父类 numberOfWheels = 2 //再进一步定制 } let bicycle = Bicycle() println("Bicycle: \(bicycle.description)") // Bicycle: 2 wheel(s)
子类可以在初始化时修改继承来的variable属性变量的值,但是不能修改继承来的constant属性。
12.4 构造器的自动继承 Automatic Initializer Inheritance
默认情况下,子类不会继承父类的构造器。但是如果满足某些情况,子类将自动继承父类构造器。这意味着,大部分情况下,你不需要覆盖父类的构造器,你可以继承父类的构造器,花很少的努力就可以让他可以被安全的使用。
假如你已经给子类引入的属性设置了初始值,那么以下规则将生效:
1)如果子类没有定义任何Designated构造器,它将自动继承父类所有的Designated构造器。
2)如果子类提供了父类所有Designated构造器的实现——不管是通过上面规则1)继承来的,还是提供了自定义的实现,它将自动继承父类所有的Convenience构造器。
即使子类增加了更多的Convenience构造器,上面的规则依然适用。
例如,定义一个基类:
class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } }
继承链条如下:
let namedMeat = Food(name: "Bacon") // namedMeat's name is "Bacon" let mysteryMeat = Food() // mysteryMeat's name is "[Unnamed]"
定义第二个类:
class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) } override convenience init(name: String) { self.init(name: name, quantity: 1) } }
它继承了父类的convenience init() 构造器。
let oneMysteryItem = RecipeIngredient() let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
定义第三个类:
class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) x \(name)" output += purchased ? " ✔" : " ✘" return output } }
因为它给所有自己引入的属性赋予了初始值,并且没有定义任何构造器,所以他将继承父类所有的Designated构造器和Convenience构造器。
你可以它使用继承来的三个构造器初始化它:
var breakfastList = [ ShoppingListItem(), ShoppingListItem(name: "Bacon"), ShoppingListItem(name: "Eggs", quantity: 6), ] breakfastList[0].name = "Orange juice" breakfastList[0].purchased = true for item in breakfastList { println(item.description) } // 1 x Orange juice ✔ // 1 x Bacon ✘ // 6 x Eggs ✘
13. Failable构造器 Failable Initializers
Failable构造器指可以失败的构造器。无效的初始化参数、缺少必要的外部资源或一些其他会让初始化失败的条件都可以触发Failable构造器。
为了应对这些可能会初始化的情况,可以使用( init?
)关键字给结构体、枚举、类定义一个或多Failable构造器。
Failable构造器会给它所初始化的类型一个可选值Optional Value。在Failable构造器中使用 return nil 表明,这个时候Failable构造器可以被触发。
注意:严格的说,构造器并没有返回值,return nil 的作用仅仅表明Failable构造器这个时候可以被触发。
struct Animal { let species: String init?(species: String) { if species.isEmpty { return nil } //如果species为空,构造失败被触发。否则对象将被成功实例化。 self.species = species } }
检查Animal是否初始化成功:
let someCreature = Animal(species: "Giraffe") // someCreature is of type Animal?, not Animal if let giraffe = someCreature { println("An animal was initialized with a species of \(giraffe.species)") } // prints "An animal was initialized with a species of Giraffe"
传空给Animal时:
let anonymousCreature = Animal(species: "") // anonymousCreature is of type Animal?, not Animal if anonymousCreature == nil { println("The anonymous creature could not be initialized") } // prints "The anonymous creature could not be initialized"
13.1 枚举类型的 Failable构造器 (Failable Initializers for Enumerations)
enum TemperatureUnit { case Kelvin, Celsius, Fahrenheit init?(symbol: Character) { switch symbol { case "K": self = .Kelvin case "C": self = .Celsius case "F": self = .Fahrenheit default: return nil } } }
使用Failable构造器选择合适的枚举成员:
let fahrenheitUnit = TemperatureUnit(symbol: "F") if fahrenheitUnit != nil { println("This is a defined temperature unit, so initialization succeeded.") } // prints "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(symbol: "X") if unknownUnit == nil { println("This is not a defined temperature unit, so initialization failed.") } // prints "This is not a defined temperature unit, so initialization failed."
13.2 使用Raw Values的枚举的Failable构造器 Failable Initializers for Enumerations with Raw Values
使用了Raw Values 的枚举类型默认拥有一个Failable构造器,init?(rawValue:)。
我们可以重新定义TemperatureUnit 枚举:
enum TemperatureUnit: Character { case Kelvin = "K", Celsius = "C", Fahrenheit = "F" } let fahrenheitUnit = TemperatureUnit(rawValue: "F") if fahrenheitUnit != nil { println("This is a defined temperature unit, so initialization succeeded.") } // prints "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(rawValue: "X") if unknownUnit == nil { println("This is not a defined temperature unit, so initialization failed.") } // prints "This is not a defined temperature unit, so initialization failed."
13.3 类的Failable构造器 Failable Initializers for Classes
值类型Value Type(结构体、枚举)的Failable构造器能在任意时刻触发失败操作。但是类类型只有在这个类引入的所有的存储属性Stored Properties被赋予初始化值后,并且所有的构造代理发生后,才能触发Failable构造器的失败操作。
下面的例子展示了如何使用一个隐式解包可选值属性 implicitly unwrapped optional property 实现Failable构造器。
class Product { let name: String! init?(name: String) { if name.isEmpty { return nil } self.name = name } }
Product类拥有一个constant类型的name属性,它不能为空字符串。为了满足这个限制,我们使用一个Failable构造器确保当构造器成功初始化实例时,name一定非空。
与结构体不同的是,在触发失败的构造的时候,Product的Failable构造器必须给name提供一个初始值。
Product类的name属性是一个隐式解包可选值类型(String!),因为它是可选值类型,这意味着name属性在被初始化之前就有默认值nil,这个默认值nil意味着Product类的name属性有一个有效的初始值。那么在Failable构造器中给name指定某个具体的值之前,如果传入一个空串,就会触发构造失败操作。
因为name是Constant,如果构造成功,你可以确保它始终为非空值,在访问name之前, 也就不需要再检查name是否为空了。
if let bowTie = Product(name: "bow tie") { // no need to check if bowTie.name == nil println("The product's name is \(bowTie.name)") } // prints "The product's name is bow tie"
13.4 Failable构造器的传播 Propagation of Initialization Failure
类、枚举和结构体中的Failable构造器可以使用它们中的其他Failable构造器,同样,子类也能访问父类中的Failable构造器。
无论哪种情况,一旦一个Failable构造器构造失败,整个构造器立即失败,不会继续执行初始化代码。
Failable构造器还可以调用非Failable构造器。
class CartItem: Product { let quantity: Int! //隐式解包类型,可Product的name属性类似。 init?(name: String, quantity: Int) { super.init(name: name) if quantity < 1 { return nil } self.quantity = quantity } }
if let twoSocks = CartItem(name: "sock", quantity: 2) { println("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)") } // prints "Item: sock, quantity: 2" if let zeroShirts = CartItem(name: "shirt", quantity: 0) { println("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)") } else { println("Unable to initialize zero shirts") } // prints "Unable to initialize zero shirts" if let oneUnnamed = CartItem(name: "", quantity: 1) { println("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)") } else { println("Unable to initialize one unnamed product") } // prints "Unable to initialize one unnamed product"
13.5 覆盖Failable构造器 Overriding a Failable Initializer
你可以像其他构造器一样覆盖父类的Failable构造器。你甚至可以使用一个非Failable构造器覆盖父类的Failable构造器,但是这种情况下,子类构造器不能向上代理调用父类的Failable构造器,一个非Failable构造器永远不能调用Failable构造器。
class Document { var name: String? //可以为non-empty string,或nil,但是不能为empty string,默认值为nil init() {}// this initializer creates a document with a nil name value init?(name: String) {// this initializer creates a document with a non-empty name value if name.isEmpty { return nil } self.name = name } }
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 } } }
14. init!Failable构造器 The init! Failable Initializer
相对于使用Failable构造器(关键字:init?)创建一个Optional Instance,你也可以使用Failable构造器(关键字:init!)创建一个隐式自解包可选实例 implicitly unwrapped optional instance 。
15. Required构造器 Required Initializers
通过 required 关键字表明一个类的构造器必须在它的每个子类中实现。
class SomeClass { required init() { // initializer implementation goes here } }
子类也可以写上 required 关键字,表明这个子类的子类也必须实现这个构造器,可以不写override关键字。
class SomeSubclass: SomeClass { required init() { // subclass implementation of the required initializer goes here } }
16. 通过闭包给属性设置默认值 Setting a Default Property Value with a Closure or Function
如果stored property的默认值需要自定义实现,可以闭包它设置默认值。
class SomeClass { let someProperty: SomeType = { // create a default value for someProperty inside this closure // someValue must be of the same type as SomeType return someValue }() }
闭包结尾的挂号表明闭包会立即执行。如果不写挂号,则是表明你是把闭包本身赋值给属性,而不是闭包的返回值。
注意:上面的例子汇总,闭包执行的时候,实例的其他部分还没有被初始化,你不能再闭包中访问实例的其他属性,你不能使用self,不能调用实例的方法。