首页 > 代码库 > Swift 的类、结构体、枚举等的构造过程Initialization(上)

Swift 的类、结构体、枚举等的构造过程Initialization(上)

构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。

 

构造过程是通过定义构造器(Initializers)来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

 

类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考析构过程。

 

 

存储型属性的初始赋值

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。

 

你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下章节将详细介绍这两种方法。

 

注意:

 

当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers)。

构造器

构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

 

下面例子中定义了一个用来保存华氏温度的结构体Fahrenheit,它拥有一个Double类型的存储型属性temperature:

 

struct Fahrenheit {
   var temperature: Double
   init() {
       temperature = 32.0
    }
}
 
var f = Fahrenheit()
println("The default temperature is\(f.temperature)° Fahrenheit")
// 输出 "The defaulttemperature is 32.0° Fahrenheit”


这个结构体定义了一个不带参数的构造器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)
// boilingPointOfWater.temperatureInCelsius是 100.0
let freezingPointOfWater =Celsius(fromKelvin: 273.15)
//freezingPointOfWater.temperatureInCelsius 是 0.0”


第一个构造器拥有一个构造参数,其外部名字为fromFahrenheit,内部名字为fahrenheit;第二个构造器也拥有一个构造参数,其外部名字为fromKelvin,内部名字为kelvin。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性temperatureInCelsius中。

 

内部和外部参数名

跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。

 

然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,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:

 

class SurveyQuestion {
   var text: String
   var response: String?
   init(text: String) {
       self.text = text
    }
   func ask() {
        println(text)
    }
}
let cheeseQuestion = SurveyQuestion(text:"Do you like cheese?")
cheeseQuestion.ask()
// 输出 "Do you likecheese?"
cheeseQuestion.response = "Yes, I dolike cheese.


调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答response声明为String?类型,或者说是可选字符串类型optional String。当SurveyQuestion实例化时,它将自动赋值为空nil,表明暂时还不存在此字符串。

 

构造过程中常量属性的修改

只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。

 

注意:

 

对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。

你可以修改上面的SurveyQuestion示例,用常量属性替代变量属性text,指明问题内容text在其创建之后不会再被修改。尽管text属性现在是常量,我们仍然可以在其类的构造器中修改它的值:

 

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()
// 输出 "How aboutbeets?"
beetsQuestion.response = "I also likebeets. (But not with cheese.)
 


默认构造器

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。

 

结构体的逐一成员构造器

除上面提到的默认构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。

 

逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。

 

下面例子中定义了一个结构体Size,它包含两个属性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()
// 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(center:size:)可以自己将origin和size的新值赋值到对应的属性中。然而尽量利用现有的构造器和它所提供的功能来实现init(center:size:)的功能,是更方便、更清晰和更直观的方法。