构造过程是为了适用某个类,结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始化和为其执行必要的准备和初始化任务。
构造过程是通过定义构造器(Initializers)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与OC中的构造器不同,Swift的构造器无需返回值,他们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行特定的清楚工作。
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
注意:当你为存储型属性设置默认值或者在构造器中为其赋值时,他们的值是被直接设置的,不会触发任何属性观测器(property observer)。
构造器
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。
下面例子定义一个用来保存华氏温度的结构体Fahrenheit,它拥有一个Double类型的存储型属性temperature:
struct Fahrenheit {
var temperature:Double
init(){
temperature = 32.0
}
}
var f = Fahrenheit()
println("The default temperature is \(f.temperature)° Fahenheit")
//The default temperature is 32.0° Fahenheit
这个结构体定义了一个不带参数的构造器init,并在里面将存储型属性temperature的值初始化为32.0(华摄氏度下水的冰点)。
默认属性值
如前所述,你可以在构造器中为存储型属性设置初始值;同样,你也可以在属性声明时为其设置默认值。
注意:如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值跟属性构造过程结合的更紧密。使用默认值能让你的构造器更简洁,更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器,构造器继承等特性。
你可以使用更简单的方式在定义结构体Fahrenheit时为属性temperature设置默认值:
struct Fahrenheit {
var temperature = 32.0
}
你可以通过输入参数和可选属性类型来定制构造过程,也可以在构造过程中修改常量属性。
构造参数
你可以在定义构造器时构造参数,为其提供定制化构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。
下面例子中定义了一个包含摄氏度温度的结构体Celsius。它定义了两个不同的构造器:init(fromFahrenheit:)和init(fromKelvin:),二者分别通过接受不同刻度表示的温度值来创建新的实例:
struct Celsius {
var temperatureInCelsius:Double = 0.0
init(fromFahrenheit fahrenheit:Double){
temperatureInCelsius = (fahrenheit - 32.0)/1.8
}
init(fromKelvin kelvin:Double){
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
println("BOIL IS \(boilingPointOfWater.temperatureInCelsius) FREE is \(freezingPointOfWater.temperatureInCelsius)")
//BOIL IS 100.0 FREE is 0.0
内部和外部参数名
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。
注意:如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线_来显示描述它的外部名,以此覆盖上面所说的默认行为。
以下例子中定义了一个结构体Color,它包含了三个常量:red,green和blue。这些属性可以存储0.0到1.0之间的值,用来指示颜色中红,绿,蓝成分的含量。
Color提供了一个构造器,其中包含三个Double类型的构造参数:
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)
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性——不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空——你都需要将它定义为可选类型optional type。可选类型的属性将自动初始化为空nil,表示这个属性是故意在初始化时设置为空的。
下面例子中定义了类SurveyQuestion,它包含一个可选字符串属性response:
struct SurveyQuestion {
var text:String
var response:String?
init(text:String){
self.text = text
}
func ask(){
println(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "DO YOU LIKE CHHEESE")
cheeseQuestion.ask()
//DO YOU LIKE CHHEESE
调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答response声明为String?类型,或者说是可选字符串类型optional String。当SurveyQuestion实例化时,它将自动赋值为空nil,表名暂时还不存在此字符串。
构造过程中常量属性的修改
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
注意:对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
你可以修改上面的SurveyQuestion示例,用常量属性替代变量属性text,指明问题内容text在其创建之后不会在被修改。尽管text属性现在是常量,我们仍然可以在其类的构造器中修改它的值:
class<pre name="code" class="html">struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
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()
//HOW ABOUT BEETS
默认构造器
Swift将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
下面例子中创建一个类ShoppingListItem,它封装了购物清单中的某一项的属性:名字(name),数量(quantity)和购买状态(purchase state)。
class ShoppingListItem{
var name:String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器(尽管代码中没有显式为name属性设置默认值,但由于name是可选字符串类型,它将默认设置为nil)。上面例子中使用默认构造器创造了一个ShoppingListItem类的实例(使用ShoppingListItem()形式的构造器语法),并将其赋值给变量item。
结构体的逐一成员构造器
除上面提到的默认构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,他们能自动获得一个逐一成员构造器。
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
下面例子中定义了一个结构体,它包含两个属性width和height。Swift可以根据这两个属性的初始赋值0.0子自动推导出他们的类型Double。
由于这两个存储型属性都有默认值,结构体Size自动获得了一个逐一成员构造器init(width:height:)。你可以用它来为Size创建新的实例:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程为构造器代理,它能减少多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理任务给本身提供的其他构造器。类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
对于值类型,你可以使用self.init在自定义的构造器中引用其他的属于相同值类型的构造器。并且你只能在构造器内部调用self.init。
注意:如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
注意:加入你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(extension)中,而不是跟值类型定义混在一起。
下面的例子将定义一个结构体Rect,用来展现几何矩形。这个例子需要两个辅助的结构体Size和Point,它们各自为其所有的属性提供了初始值0.0.
struct Size {
var width = 0.0,height = 0.0
}
struct Point{
var x = 0.0, y = 0.0
}
你可以通过以下三种方式为Rect创建实例——使用默认的0值来初始化origin和size属性;使用特定的origin和size实例来初始化;使用页顶的center和size来初始化。
在下面Rect结构体定义中,我们为这三种方式提供了三个自定义的构造器:
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()
//basic 的原点是(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(center:size:)可以自己将origin和size的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现init(center:size:)的功能,是更方便,更清晰和更直观的方法。
注意:如果你想用另外一种不需要自己定义init()和init(origin:size:)的方式来实现这个例子,请参考扩展。
类的继承和构造过程
类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
Swift提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是制定构造器和便利构造器。
制定构造器和便利构造器
制定构造器是类中最主要的构造器。一个制定构造器将初始化类中提供的所有属性,并根据父类往上调用父类的构造器来实现父类的初始化。
每一个类都必须拥有至少一个制定构造器。在某些情况下,许多类通过继承了父类的制定构造器而满足了这个条件。(参考自动构造器的继承)。
便利构造器是类中比较次要的,辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰。
构造器链
为了简化指定构造器和便利构造器之间的调用关系,Swift采用以下三条规则来限制构造器之间的代理调用:
规则1:
指定构造器必须调用其直接父类的指定构造器。
规则2:
便利构造器必须调用同一类中定义的其他构造器。
规则3:
便利构造器必须最终以调用一个指定构造器结束。
更方便记忆的方法是:
指定构造器必须总是向上代理,便利构造器总是横向代理。
两段式构造过程
Swift中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入他们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,他给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外的赋予不同的值。
注意:
Swift的两段式构造过程跟OC中的构造过程类似。最主要的区别在于阶段1,OC给每一个属性赋值0或空值(比如0或者nil)。Swift的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0或nil作为合法默认值的情况。
Swift编译器将执行4种有效的安全检查,以确保两段式构造过程能顺利完成:
安全检查1:
指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
安全检查2:
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查3:
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没有这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查4:
构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,也不能饮用self的值。
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段1
1:某个指定构造器或便利构造器被调用;
2:完成新实例内存的分配,但此时内存还没有被初始化;
3:指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的额内存完成初始化;
4:指定构造器将调用父类的构造器,完成父类属性的初始化;
5:这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
6:当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已赋值,这个实例的内存被认为已经完全初始化,此时阶段1完成。
阶段2
1:从顶部构造器一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等;
2:最终,任意构造器链中的便利构造器可以有机会定制实例和使用self;