Swift-2.14构造过程

本页包含内容:html

构造过程是使用类、结构体或枚举类型的实例以前的准备过程。在新实例可用前必须执行这个过程,具体操做包括设置实例中每一个存储型属性的初始值和执行其余必须的设置或初始化工做。ios

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

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

存储属性的初始赋值

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

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

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

构造器

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

init() { // 在此处执行构造过程 } 

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

struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() print("The default temperature is \(f.temperature)° Fahrenheit") // 输出 "The default temperature 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 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,它包含了三个常量:redgreenblue。这些属性能够存储0.01.0之间的值,用来指示颜色中红、绿、蓝成分的含量。

Color提供了一个构造器,其中包含三个Double类型的构造参数。Color也能够提供第二个构造器,它只包含名为whiteDouble类型的参数,它被用于给上述三个构造参数赋予一样的值。

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 } } 

两种构造器都能用于建立一个新的Color实例,你须要为构造器每一个外部参数传值:

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) // 报编译时错误,须要外部名称 

不带外部名的构造器参数

若是你不但愿为构造器的某个参数提供外部名字,你可使用下划线(_)来显式描述它的外部名,以此重写上面所说的默认行为。

下面是以前Celsius例子的扩展,跟以前相比添加了一个带有Double类型参数的构造器,其外部名用_代替:

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) // bodyTemperature.temperatureInCelsius 为 37.0 

调用Celsius(37.0)意图明确,不须要外部参数名称。所以适合使用init(_ celsius: Double)这样的构造器,从而能够经过提供Double类型的参数值调用构造器,而不须要加上外部名。

可选属性类型

若是你定制的类型包含一个逻辑上容许取值为空的存储型属性——不管是由于它没法在初始化时赋值,仍是由于它在以后某个时间点能够赋值为空——你都须要将它定义为可选类型optional type。可选类型的属性将自动初始化为nil,表示这个属性是有意在初始化时设置为空的。

下面例子中定义了类SurveyQuestion,它包含一个可选字符串属性response

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

调查问题的答案在回答前是没法肯定的,所以咱们将属性response声明为String?类型,或者说是可选字符串类型optional String。当SurveyQuestion实例化时,它将自动赋值为nil,代表此字符串暂时尚未值。

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

你能够在构造过程当中的任意时间点修改常量属性的值,只要在构造过程结束时是一个肯定的值。一旦常量属性被赋值,它将永远不可更改。

注意
对于类的实例来讲,它的常量属性只能在定义它的类的构造过程当中修改;不能在子类中修改。

你能够修改上面的SurveyQuestion示例,用常量属性替代变量属性text,表示问题内容textSurveyQuestion的实例被建立以后不会再被修改。尽管text属性如今是常量,咱们仍然能够在类的构造器中设置它的值:

class SurveyQuestion { let text: String var response: String? init(text: String) { self.text = text } func ask() { print(text) } } let beetsQuestion = SurveyQuestion(text: "How about beets?") beetsQuestion.ask() // 输出 "How about beets?" beetsQuestion.response = "I also like beets. (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,它包含两个属性widthheight。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,用来表明几何矩形。这个例子须要两个辅助的结构体SizePoint,它们各自为其全部的属性提供了初始值0.0

struct Size { var width = 0.0, height = 0.0 } struct Point { var x = 0.0, y = 0.0 } 

你能够经过如下三种方式为Rect建立实例——使用被初始化为默认值的originsize属性来初始化;提供指定的originsize实例来初始化;提供指定的centersize来初始化。在下面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实例,它的originsize属性都使用定义时的默认值Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

let basicRect = Rect() // basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0) 

第二个Rect构造器init(origin:size:),在功能上跟结构体在没有自定义构造器时得到的逐一成员构造器是同样的。这个构造器只是简单地将originsize的参数值赋给对应的存储型属性:

let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) // originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0) 

第三个Rect构造器init(center:size:)稍微复杂一点。它先经过centersize的值计算出origin的坐标,而后再调用(或者说代理给)init(origin:size:)构造器来将新的originsize值赋值到对应的属性中:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0)) // centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0) 

构造器init(center:size:)能够直接将originsize的新值赋值到对应的属性中。然而,利用刚好提供了相关功能的现有构造器会更为方便,构造器init(center:size:)的意图也会更加清晰。

注意
若是你想用另一种不须要本身定义init()init(origin:size:)的方式来实现这个例子,请参考扩展

类的继承和构造过程

类里面的全部存储型属性——包括全部继承自父类的属性——都必须在构造过程当中设置初始值。

Swift 为类类型提供了两种构造器来确保实例中全部存储型属性都能得到初始值,它们分别是指定构造器和便利构造器。

指定构造器和便利构造器

指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的全部属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

每个类都必须拥有至少一个指定构造器。在某些状况下,许多类经过继承了父类中的指定构造器而知足了这个条件。具体内容请参考后续章节构造器的自动继承

便利构造器是类中比较次要的、辅助型的构造器。你能够定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也能够定义便利构造器来建立一个特殊用途或特定输入值的实例。

你应当只在必要的时候为类提供便利构造器,比方说某种状况下经过使用便利构造器来快捷调用某个指定构造器,可以节省更多开发时间并让类的构造过程更清晰明了。

指定构造器和便利构造器的语法

类的指定构造器的写法跟值类型简单构造器同样:

init(parameters) { statements } 

便利构造器也采用相一样式的写法,但须要在init关键字以前放置convenience关键字,并使用空格将它们俩分开:

convenience init(parameters) { statements } 

类的构造器代理规则

为了简化指定构造器和便利构造器之间的调用关系,Swift 采用如下三条规则来限制构造器之间的代理调用:

规则 1

指定构造器必须调用其直接父类的的指定构造器。

规则 2

便利构造器必须调用同一类中定义的其它构造器。

规则 3

便利构造器必须最终致使一个指定构造器被调用。

一个更方便记忆的方法是:

  • 指定构造器必须老是向上代理
  • 便利构造器必须老是横向代理

这些规则能够经过下面图例来讲明:

构造器代理图

如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另一个便利构造器,然后者又调用了惟一的指定构造器。这知足了上面提到的规则 2 和 3。这个父类没有本身的父类,因此规则 1 没有用到。

子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,由于它只能调用同一个类里的其余构造器。这知足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中惟一的指定构造器,这知足了规则 1。

注意
这些规则不会影响类的实例如何建立。任何上图中展现的构造器均可以用来建立彻底初始化的实例。这些规则只影响类定义如何实现。

下面图例中展现了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的做用,在类的构造器链上简化了类之间的相互关系。

复杂构造器代理图

两段式构造过程

Swift 中类的构造过程包含两个阶段。第一个阶段,每一个存储型属性经过引入它们的类的构造器来设置初始值。当每个存储型属性值被肯定后,第二阶段开始,它给每一个类一次机会在新实例准备使用以前进一步定制它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每一个类彻底的灵活性。两段式构造过程能够防止属性值在初始化以前被访问,也能够防止属性被另一个构造器意外地赋予不一样的值。

注意
Swift 的两段式构造过程跟 Objective-C 中的构造过程相似。最主要的区别在于阶段 1,Objective-C 给每个属性赋值0或空值(好比说0nil)。Swift 的构造流程则更加灵活,它容许你设置定制的初始值,并自如应对某些属性不能以0nil做为合法默认值的状况。

Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成:

安全检查 1

指定构造器必须保证它所在类引入的全部属性都必须先初始化完成,以后才能将其它构造任务向上代理给父类中的构造器。

如上所述,一个对象的内存只有在其全部存储型属性肯定以后才能彻底初始化。为了知足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理以前先完成初始化。

安全检查 2

指定构造器必须先向上代理调用父类构造器,而后再为继承的属性设置新值。若是没这么作,指定构造器赋予的新值将被父类中的构造器所覆盖。

安全检查 3

便利构造器必须先代理调用同一类中的其它构造器,而后再为任意属性赋新值。若是没这么作,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

安全检查 4

构造器在第一阶段构造完成以前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self做为一个值。

类实例在第一阶段结束之前并非彻底有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。

如下是两段式构造过程当中基于上述安全检查的构造流程展现:

阶段 1
  • 某个指定构造器或便利构造器被调用。
  • 完成新实例内存的分配,但此时内存尚未被初始化。
  • 指定构造器确保其所在类引入的全部存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器将调用父类的构造器,完成父类属性的初始化。
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
  • 当到达了构造器链最顶部,且已确保全部实例包含的存储型属性都已经赋值,这个实例的内存被认为已经彻底初始化。此时阶段 1 完成。
阶段 2
  • 从顶部构造器链一直往下,每一个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时能够访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器能够有机会定制实例和使用self

下图展现了在假定的子类和父类之间的构造阶段 1:

构建过程阶段1

在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时无法修改任何属性,它把构造任务代理给同一类中的指定构造器。

如安全检查 1 所示,指定构造器将确保全部子类的属性都有值。而后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构造过程。

父类中的指定构造器确保全部父类的属性都有值。因为没有更多的父类须要初始化,也就无需继续向上代理。

一旦父类中全部属性都有了初始值,实例的内存被认为是彻底初始化,阶段 1 完成。

如下展现了相同构造过程的阶段 2:

构建过程阶段2

父类中的指定构造器如今有机会进一步来定制实例(尽管这不是必须的)。

一旦父类中的指定构造器完成调用,子类中的指定构造器能够执行更多的定制操做(这也不是必须的)。

最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器能够执行更多的定制操做。

构造器的继承和重写

跟 Objective-C 中的子类不一样,Swift 中的子类默认状况下不会继承父类的构造器。Swift 的这种机制能够防止一个父类的简单构造器被一个更专业的子类继承,并被错误地用来建立子类的实例。

注意
父类的构造器仅会在安全和适当的状况下被继承。具体内容请参考后续章节构造器的自动继承

假如你但愿自定义的子类中能提供一个或多个跟父类相同的构造器,你能够在子类中提供这些构造器的自定义实现。

当你在编写一个和父类中指定构造器相匹配的子类构造器时,你其实是在重写父类的这个指定构造器。所以,你必须在定义子类构造器时带上override修饰符。即便你重写的是系统自动提供的默认构造器,也须要带上override修饰符,具体内容请参考默认构造器

正如重写属性,方法或者是下标脚本,override修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。

注意
当你重写一个父类的指定构造器时,你老是须要写override修饰符,即便你的子类将父类的指定构造器重写为了便利构造器。

相反,若是你编写了一个和父类便利构造器相匹配的子类构造器,因为子类不能直接调用父类的便利构造器(每一个规则都在上文类的构造器代理规则有所描述),所以,严格意义上来说,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不须要加override前缀。

在下面的例子中定义了一个叫Vehicle的基类。基类中声明了一个存储型属性numberOfWheels,它是值为0Int类型的存储型属性。numberOfWheels属性用于建立名为descrpiptionString类型的计算型属性:

class Vehicle { var numberOfWheels = 0 var description: String { return "\(numberOfWheels) wheel(s)" } } 

Vehicle类只为存储型属性提供默认值,而不自定义构造器。所以,它会自动得到一个默认构造器,具体内容请参考默认构造器。自动得到的默认构造器总会是类中的指定构造器,它能够用于建立numberOfWheels0Vehicle实例:

let vehicle = Vehicle() print("Vehicle: \(vehicle.description)") // Vehicle: 0 wheel(s) 

下面例子中定义了一个Vehicle的子类Bicycle

class Bicycle: Vehicle { override init() { super.init() numberOfWheels = 2 } } 

子类Bicycle定义了一个自定义指定构造器init()。这个指定构造器和父类的指定构造器相匹配,因此Bicycle中的指定构造器须要带上override修饰符。

Bicycle的构造器init()以调用super.init()方法开始,这个方法的做用是调用Bicycle的父类Vehicle的默认构造器。这样能够确保Bicycle在修改属性以前,它所继承的属性numberOfWheels能被Vehicle类初始化。在调用super.init()以后,属性numberOfWheels的原值被新值2替换。

若是你建立一个Bicycle实例,你能够调用继承的description计算型属性去查看属性numberOfWheels是否有改变:

let bicycle = Bicycle() print("Bicycle: \(bicycle.description)") // Bicycle: 2 wheel(s) 

注意
子类能够在初始化时修改继承来的变量属性,可是不能修改继承来的常量属性。

构造器的自动继承

如上所述,子类在默认状况下不会继承父类的构造器。可是若是知足特定条件,父类构造器是能够被自动继承的。在实践中,这意味着对于许多常见场景你没必要重写父类的构造器,而且能够在安全的状况下以最小的代价继承父类的构造器。

假设你为子类中引入的全部新属性都提供了默认值,如下 2 个规则适用:

规则 1

若是子类没有定义任何指定构造器,它将自动继承全部父类的指定构造器。

规则 2

若是子类提供了全部父类指定构造器的实现——不管是经过规则 1 继承过来的,仍是提供了自定义实现——它将自动继承全部父类的便利构造器。(即便属性没有默认值,只要实现了父类的全部指定构造器,就会自动继承父类的全部便利构造器)

即便你在子类中添加了更多的便利构造器,这两条规则仍然适用。

注意
对于规则 2,子类能够将父类的指定构造器实现为便利构造器。

指定构造器和便利构造器实践

接下来的例子将在实践中展现指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类FoodRecipeIngredient以及ShoppingListItem的类层次结构,并将演示它们的构造器是如何相互做用的。

类层次中的基类是Food,它是一个简单的用来封装食物名字的类。Food类引入了一个叫作nameString类型的属性,而且提供了两个构造器来建立Food实例:

class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } } 

下图中展现了Food的构造器链:

Food构造器链

类类型没有默认的逐一成员构造器,因此Food类提供了一个接受单一参数name的指定构造器。这个构造器可使用一个特定的名字来建立新的Food实例:

let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon” 

Food类中的构造器init(name: String)被定义为一个指定构造器,由于它能确保Food实例的全部存储型属性都被初始化。Food类没有父类,因此init(name: String)构造器不须要调用super.init()来完成构造过程。

Food类一样提供了一个没有参数的便利构造器init()。这个init()构造器为新食物提供了一个默认的占位名字,经过横向代理到指定构造器init(name: String)并给参数name传值[Unnamed]来实现:

let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed] 

类层级中的第二个类是Food的子类RecipeIngredientRecipeIngredient类构建了食谱中的一味调味剂。它引入了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类的构造器链:

RecipeIngredient构造器

RecipeIngredient类拥有一个指定构造器init(name: String, quantity: Int),它能够用来填充RecipeIngredient实例的全部属性值。这个构造器一开始先将传入的quantity参数赋值给quantity属性,这个属性也是惟一在RecipeIngredient中新引入的属性。随后,构造器向上代理到父类Foodinit(name: String)。这个过程知足两段式构造过程中的安全检查 1。

RecipeIngredient还定义了一个便利构造器init(name: String),它只经过name来建立RecipeIngredient的实例。这个便利构造器假设任意RecipeIngredient实例的quantity1,因此不须要显式指明数量便可建立出实例。这个便利构造器的定义能够更加方便和快捷地建立实例,而且避免了建立多个quantity1RecipeIngredient实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为quantity参数传递1

注意,RecipeIngredient的便利构造器init(name: String)使用了跟Food中指定构造器init(name: String)相同的参数。因为这个便利构造器重写了父类的指定构造器init(name: String),所以必须在前面使用override修饰符(参见构造器的继承和重写)。

尽管RecipeIngredient将父类的指定构造器重写为了便利构造器,它依然提供了父类的全部指定构造器的实现。所以,RecipeIngredient会自动继承父类的全部便利构造器。

在这个例子中,RecipeIngredient的父类是Food,它有一个便利构造器init()。这个便利构造器会被RecipeIngredient继承。这个继承版本的init()在功能上跟Food提供的版本是同样的,只是它会代理到RecipeIngredient版本的init(name: String)而不是Food提供的版本。

全部的这三种构造器均可以用来建立新的RecipeIngredient实例:

let oneMysteryItem = RecipeIngredient() let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6) 

类层级中第三个也是最后一个类是RecipeIngredient的子类,叫作ShoppingListItem。这个类构建了购物单中出现的某一种调味料。

购物单中的每一项老是从未购买状态开始的。为了呈现这一事实,ShoppingListItem引入了一个布尔类型的属性purchased,它的默认值是falseShoppingListItem还添加了一个计算型属性description,它提供了关于ShoppingListItem实例的一些文字描述:

class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) x \(name)" output += purchased ? " ✔" : " ✘" return output } } 

注意
ShoppingListItem没有定义构造器来为purchased提供初始值,由于添加到购物单的物品的初始状态老是未购买。

因为它为本身引入的全部属性都提供了默认值,而且本身没有定义任何构造器,ShoppingListItem将自动继承全部父类中的指定构造器和便利构造器。

下图展现了这三个类的构造器链:

三类构造器图

你可使用所有三个继承来的构造器来建立ShoppingListItem的新实例:

var breakfastList = [ ShoppingListItem(), ShoppingListItem(name: "Bacon"), ShoppingListItem(name: "Eggs", quantity: 6), ] breakfastList[0].name = "Orange juice" breakfastList[0].purchased = true for item in breakfastList { print(item.description) } // 1 x orange juice ✔ // 1 x bacon ✘ // 6 x eggs ✘ 

如上所述,例子中经过字面量方式建立了一个数组breakfastList,它包含了三个ShoppingListItem实例,所以数组的类型也能被自动推导为[ShoppingListItem]。在数组建立完以后,数组中第一个ShoppingListItem实例的名字从[Unnamed]更改成Orange juice,并标记为已购买。打印数组中每一个元素的描述显示了它们都已按照预期被赋值。

可失败构造器

若是一个类、结构体或枚举类型的对象,在构造过程当中有可能失败,则为其定义一个可失败构造器。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺乏某种所需的外部资源,又或是不知足某种必要的条件等。

为了妥善处理这种构造过程当中可能会失败的状况。你能够在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)

注意
可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。

可失败构造器会建立一个类型为自身类型的可选类型的对象。你经过return nil语句来代表可失败构造器在何种状况下应该“失败”。

注意
严格来讲,构造器都不支持返回值。由于构造器自己的做用,只是为了确保对象能被正确构造。所以你只是用return nil代表可失败构造器构造失败,而不要用关键字return来代表构形成功。

下例中,定义了一个名为Animal的结构体,其中有一个名为speciesString类型的常量属性。同时该结构体还定义了一个接受一个名为speciesString类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。若是为空字符串,则构造失败。不然,species属性被赋值,构形成功。

struct Animal { let species: String init?(species: String) { if species.isEmpty { return nil } self.species = species } } 

你能够经过该可失败构造器来构建一个Animal的实例,并检查构造过程是否成功:

let someCreature = Animal(species: "Giraffe") // someCreature 的类型是 Animal? 而不是 Animal if let giraffe = someCreature { print("An animal was initialized with a species of \(giraffe.species)") } // 打印 "An animal was initialized with a species of Giraffe" 

若是你给该可失败构造器传入一个空字符串做为其参数,则会致使构造失败:

let anonymousCreature = Animal(species: "") // anonymousCreature 的类型是 Animal?, 而不是 Animal if anonymousCreature == nil { print("The anonymous creature could not be initialized") } // 打印 "The anonymous creature could not be initialized" 

注意
空字符串(如"",而不是"Giraffe")和一个值为nil的可选类型的字符串是两个彻底不一样的概念。上例中的空字符串("")实际上是一个有效的,非可选类型的字符串。这里咱们之因此让Animal的可失败构造器构造失败,只是由于对于Animal这个类的species属性来讲,它更适合有一个具体的值,而不是空字符串。

枚举类型的可失败构造器

你能够经过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。若是提供的参数没法匹配任何枚举成员,则构造失败。

下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(KelvinCelsius,和Fahrenheit),以及一个根据Character值找出所对应的枚举成员的可失败构造器:

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 } } } 

你能够利用该可失败构造器在三个枚举成员中获取一个相匹配的枚举成员,当参数的值不能与任何枚举成员相匹配时,则构造失败:

let fahrenheitUnit = TemperatureUnit(symbol: "F") if fahrenheitUnit != nil { print("This is a defined temperature unit, so initialization succeeded.") } // 打印 "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(symbol: "X") if unknownUnit == nil { print("This is not a defined temperature unit, so initialization failed.") } // 打印 "This is not a defined temperature unit, so initialization failed." 

带原始值的枚举类型的可失败构造器

带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值类型一致,若是该参数的值可以和某个枚举成员的原始值匹配,则该构造器会构造相应的枚举成员,不然构造失败。

所以上面的TemperatureUnit的例子能够重写为:

enum TemperatureUnit: Character { case Kelvin = "K", Celsius = "C", Fahrenheit = "F" } let fahrenheitUnit = TemperatureUnit(rawValue: "F") if fahrenheitUnit != nil { print("This is a defined temperature unit, so initialization succeeded.") } // 打印 "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(rawValue: "X") if unknownUnit == nil { print("This is not a defined temperature unit, so initialization failed.") } // 打印 "This is not a defined temperature unit, so initialization failed." 

类的可失败构造器

值类型(也就是结构体或枚举)的可失败构造器,能够在构造过程当中的任意时间点触发构造失败。好比在前面的例子中,结构体Animal的可失败构造器在构造过程一开始就触发了构造失败,甚至在species属性被初始化前。

而对类而言,可失败构造器只能在类引入的全部存储型属性被初始化后,以及构造器代理调用完成后,才能触发构造失败。

下面例子展现了如何在类的可失败构造器中使用隐式解包可选类型来知足上述要求:

class Product { let name: String! init?(name: String) { self.name = name if name.isEmpty { return nil } } } 

上面定义的Product类和以前的Animal结构体很类似。Product类有一个不能为空字符串的常量属性name。为了强制这个要求,Product类使用了可失败构造器确保这个属性的值不是空字符串后,才容许构形成功。

毕竟,Product是一个类而不是结构体,这意味着不一样于AnimalProduct类的全部可失败构造器必须给name属性一个初始值,而后才能触发构造失败。

上面的例子中,Product类的name属性被定义为隐式解包可选字符串类型(String!)。由于它是一个可选类型,因此它在构造过程当中被赋值前,具备默认值nil。这个默认值nil意味着Product类引入的全部存储型属性都有一个有效的初始值。所以,一旦传入一个空字符串,该可失败构造器能够在name属性被赋值前触发构造失败。

译者注
上面的示例代码和描述并不相符,根据描述,if name.isEmpty { return nil }这句代码应该在self.name = name以前,而这却会致使编译错误error: all stored properties of a class instance must be initialized before returning nil from an initializer,除非将let name: String!改成var name: String!

由于name属性是一个常量,因此一旦构形成功,name属性确定有一个非nil的值。即便它被定义为隐式解包可选类型,也彻底能够放心大胆地直接访问,而不用检查name属性的值是否为nil

if let bowTie = Product(name: "bow tie") { // 不须要检查 bowTie.name 是否为 nil print("The product's name is \(bowTie.name)") } // 打印 "The product's name is bow tie" 

构造失败的传递

类,结构体,枚举的可失败构造器能够横向代理到类型中的其余可失败构造器。相似的,子类的可失败构造器也能向上代理到父类的可失败构造器。

不管是向上代理仍是横向代理,若是你代理到的其余可失败构造器触发构造失败,整个构造过程将当即终止,接下来的任何构造代码不会再被执行。

注意
可失败构造器也能够代理到其它的非可失败构造器。经过这种方式,你能够增长一个可能的失败状态到现有的构造过程当中。

下面这个例子,定义了一个名为CartItemProduct类的子类。这个类创建了一个在线购物车中的物品的模型,它有一个名为quantity的常量存储型属性,并确保该属性的值至少为1

class CartItem: Product { let quantity: Int! init?(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) if quantity < 1 { return nil } } } 

Product类中的name属性相似,CartItem类中的quantity属性也是隐式解包可选类型。这意味着在构造过程当中,该属性在被赋予特定的值以前能有一个默认的初始值nil

该可失败构造器以向上代理到父类的可失败构造器init(name:)开始。这知足了可失败构造器在触发构造失败前必须老是完成构造器代理调用这个条件。

若是因为name的值为空字符串而致使父类的可失败构造器构造失败,则CartIem类的整个构造过程都将当即失败,以后的构造代码将不会再被执行。若是父类构形成功,CartIem的可失败构造器会进一步验证quantity的值是否不小于1

译者注
上面的示例代码和描述也不相符,根据描述,self.quantity = quantity这句代码应该放在最后一行,而这却会致使编译错误error: property 'self.quantity' not initialized at super.init call,除非将let quantity: Int!改成var quantity: Int!

若是你构造一个name的值为非空字符串,quantity的值不小于1CartItem实例,则可成功构造:

if let twoSocks = CartItem(name: "sock", quantity: 2) { print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)") } // 打印 "Item: sock, quantity: 2" 

若是你试图构造一个quantity的值为0CartItem实例, 则CartItem的可失败构造器会触发构造失败:

if let zeroShirts = CartItem(name: "shirt", quantity: 0) { print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)") } else { print("Unable to initialize zero shirts") } // 打印 "Unable to initialize zero shirts" 

相似的,若是你试图构造一个name的值为空字符串的CartItem实例,则父类Product的可失败构造器会触发构造失败:

if let oneUnnamed = CartItem(name: "", quantity: 1) { print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)") } else { print("Unable to initialize one unnamed product") } // 打印 "Unable to initialize one unnamed product" 

重写一个可失败构造器

如同其它的构造器,你能够在子类中重写父类的可失败构造器。或者你也能够用子类的非可失败构造器重写一个父类的可失败构造器。这使你能够定义一个不会构造失败的子类,即便父类的构造器容许构造失败。

注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的惟一方式是对父类的可失败构造器的返回值进行强制解包。

注意
你能够用非可失败构造器重写可失败构造器,但反过来却不行。

下例定义了一个名为Document的类,name属性的值必须为一个非空字符串或nil,但不能是一个空字符串:

class Document { var name: String? // 该构造器建立了一个 name 属性的值为 nil 的 document 实例 init() {} // 该构造器建立了一个 name 属性的值为非空字符串的 document 实例 init?(name: String) { self.name = name if name.isEmpty { return nil } } } 

下面这个例子,定义了一个Document类的子类AutomaticallyNamedDocument。这个子类重写了父类的两个指定构造器,确保了不管是使用init()构造器,仍是使用init(name:)构造器并为参数传递空字符串,生成的实例中的name属性总有初始"[Untitled]"

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 } } } 

AutomaticallyNamedDocument用一个非可失败构造器init(name:)重写了父类的可失败构造器init?(name:)。由于子类用另外一种方式处理了空字符串的状况,因此再也不须要一个可失败构造器,所以子类用一个非可失败构造器代替了父类的可失败构造器。

你能够在子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。好比,下面的UntitledDocument子类的name属性的值老是"[Untitled]",它在构造过程当中使用了父类的可失败构造器init?(name:)

class UntitledDocument: Document { override init() { super.init(name: "[Untitled]")! } } 

在这个例子中,若是在调用父类的可失败构造器init?(name:)时传入的是空字符串,那么强制解包操做会引起运行时错误。不过,由于这里是经过非空的字符串常量来调用它,因此并不会发生运行时错误。

可失败构造器 init!

一般来讲咱们经过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也能够经过在init后面添加惊叹号的方式来定义一个可失败构造器((init!)),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。

你能够在init?中代理到init!,反之亦然。你也能够用init?重写init!,反之亦然。你还能够用init代理到init!,不过,一旦init!构造失败,则会触发一个断言。

必要构造器

在类的构造器前添加required修饰符代表全部该类的子类都必须实现该构造器:

class SomeClass { required init() { // 构造器的实现代码 } } 

在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,代表该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不须要添加override修饰符:

class SomeSubclass: SomeClass { required init() { // 构造器的实现代码 } } 

注意
若是子类继承的构造器能知足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。

经过闭包或函数设置属性的默认值

若是某个存储型属性的默认值须要一些定制或设置,你可使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被建立时,对应的闭包或函数会被调用,而它们的返回值会当作默认值赋值给这个属性。

这种类型的闭包或函数一般会建立一个跟属性类型相同的临时变量,而后修改它的值以知足预期的初始状态,最后返回这个临时变量,做为属性的默认值。

下面介绍了如何用闭包为属性提供默认值:

class SomeClass { let someProperty: SomeType = { // 在这个闭包中给 someProperty 建立一个默认值 // someValue 必须和 SomeType 类型相同 return someValue }() } 

注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 当即执行此闭包。若是你忽略了这对括号,至关于将闭包自己做为值赋值给了属性,而不是将闭包的返回值赋值给属性。

注意
若是你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都尚未初始化。这意味着你不能在闭包里访问其它属性,即便这些属性有默认值。一样,你也不能使用隐式的self属性,或者调用任何实例方法。

下面例子中定义了一个结构体Checkerboard,它构建了西洋跳棋游戏的棋盘:

西洋跳棋棋盘

西洋跳棋游戏在一副黑白格交替的10x10的棋盘中进行。为了呈现这副游戏棋盘,Checkerboard结构体定义了一个属性boardColors,它是一个包含100Bool值的数组。在数组中,值为true的元素表示一个黑格,值为false的元素表示一个白格。数组中第一个元素表明棋盘上左上角的格子,最后一个元素表明棋盘上右下角的格子。

boardColor数组是经过一个闭包来初始化并设置颜色值的:

struct Checkerboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...10 { for j in 1...10 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 10) + column] } } 

每当一个新的Checkerboard实例被建立时,赋值闭包会被执行,boardColors的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每一个格子对应的颜色,并将这些值保存到一个临时数组temporaryBoard中,最后在构建完成时将此数组做为闭包返回值返回。这个返回的数组会保存到boardColors中,并能够经过工具函数squareIsBlackAtRow来查询:

let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) // 打印 "true" print(board.squareIsBlackAtRow(9, column: 9)) // 打印 "false"
相关文章
相关标签/搜索