构造过程是为了使用某个类、结构体或枚举类型的实例而进行的初始化过程。
类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行特定的清除工作。
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
注意:当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers)
构造器
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。
struct Fahrenheit { var temperature: Double init() { // 构造器的最简形式 temperature = 32.0 } } var f = Fahrenheit() println("\(f.temperature)") // 输出 "32.0”
默认属性值
你可以在构造器中为存储型属性设置初始值。同样也可以在属性声明时为其设置默认值。
以下在属性声明时为其设置默认值
struct Fahrenheit { var temperature = 32.0 }
自定义构造器
你可以通过输入参数和可选属性类型来定制构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。
构造参数
你可以在定义构造器时提供构造参数,为其提供定制化构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。
struct Celsius {
var temperatureInCelsius: Double = 0.0
// 第一个构造器拥有一个构造参数,其外部名字为fromFahrenheit,内部名字为fahrenheit
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
// 第二个构造器也拥有一个构造参数,其外部名字为fromKelvin,内部名字为kelvin。
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0”
内部和外部参数名
在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名。
struct Color { let red = 0.0, green = 0.0, blue = 0.0 init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } }
// 每当你创建一个新的Color实例,你都需要通过三种颜色的外部参数名来传值 let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) // 如果不通过外部参数名字传值,你是没法调用这个构造器的 let veryGreen = Color(0.0, 1.0, 0.0) // 报编译时错误,需要外部名称
可选属性类型
可选类型的属性将自动初始化为空nil,表示这个属性是故意在初始化时设置为空的。
构造过程中常量属性的修改
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
默认构造器
Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem()
注意:要想使用默认构造器,须得所有的属性都有一个默认值,可选值除外,因为系统会自动分配一个nil值。
结构体的逐一成员构造器
除上面提到的默认构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
struct Size { var width = 0.0, height = 0.0 } let twoByTwo = Size(width: 2.0, height: 2.0)
值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
对于值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init()。
注意,如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
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构造器init(),在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。调用这个构造器将返回一个Rect实例,它的origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0): let basicRect = Rect() // basicRect 的原点是 (0.0, 0.0),尺寸是 (0.0, 0.0) // 第二个Rect构造器init(origin:size:),在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单的将origin和size的参数值赋给对应的存储型属性: let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) // originRect 的原点是 (2.0, 2.0),尺寸是 (5.0, 5.0) // 第三个Rect构造器init(center:size:)稍微复杂一点。它先通过center和size的值计算出origin的坐标。然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中: let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size:Size(width: 3.0, height: 3.0)) // centerRect 的原点是 (2.5, 2.5),尺寸是 (3.0, 3.0)
类的继承和构造过程
指定构造器和便利构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
指定构造器和便利构造器的语法
类的指定构造器的写法跟值类型简单构造器一样:
init(parameters) {
statements
}
便利构造器也采用相同样式的写法,但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开:
convenience init(parameters) {
statements
}
指定构造器和便利构造器实战
class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } }
下图中展示了Food的构造器链:
class Food { var name: String // 单一参数的构造器 init(name: String) { self.name = name } // 便利构造器 convenience init() { self.init(name: "[Unnamed]") } } let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon”
Food类没有父类,所以init(name: String)构造器不需要调用super.init()来完成构造。
下面调用便利构造器:
let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed]
类层级中的第二个类是Food的子类RecipeIngredient。RecipeIngredient类构建了食谱中的一味调味剂。它引入了Int类型的数量属性quantity(以及从Food继承过来的name属性),并且定义了两个构造器来创建RecipeIngredient实例:
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) } }
下图中展示了RecipeIngredient类的构造器链:
let oneMysteryItem = RecipeIngredient() // 调用了从父类集成的便利构造器,内部又调用了RecipeIngredient的便利构造器 let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
构造器链
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同一类中定义的其它构造器。
规则 3
便利构造器必须最终以调用一个指定构造器结束。
- 指定构造器必须总是向上代理
- 便利构造器必须总是横向代理
两段式构造过程
Swift 中类的构造过程包含两个阶段。
第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成:
安全检查 1
一个对象的内存只有在其所有存储型属性确定之后才能完成初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查 3
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self的值。
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段 1
- 某个指定构造器或便利构造器被调用;
- 完成新实例内存的分配,但此时内存还没有被初始化;
- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
- 指定构造器将调用父类的构造器,完成父类属性的初始化;
- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
- 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段 2
构造器的继承和重载
Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
注意:与方法、属性和下标不同,在重载构造器时你没有必要使用关键字override。
自动构造器的继承
子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。
规则 1
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
规则 2
如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。
通过闭包和函数来设置属性的默认值
class SomeClass { let someProperty: SomeType = { // 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同 return someValue }() }
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意:如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
2015-03-23
16:44:54