该页面包含内容:
存储属性的初始赋值自定义构造过程默认初始值设定项值类型的构造函数代理类的继承和构造过程可失败的初始值设定项必需的初始值设定项通过闭包或函数为属性设置默认值构造过程是使用类、结构体或准备过程完成的在枚举类型的实例之前。此过程必须在新实例可用之前执行。具体操作包括设置实例中每个存储属性的初始值以及执行其他必要的设置或初始化工作。
构造过程是通过定义初始值设定项来实现的,可以将其视为用于创建特定类型的新实例的特殊方法。与Objective-C 中的构造函数不同,Swift 的构造函数不需要返回值。他们的主要任务是确保新实例在首次使用之前正确初始化。
类的实例还可以通过定义析构函数在实例被释放之前执行特定的清理工作。要了解有关析构函数的更多信息,请参阅析构函数过程。
存储属性的初始赋值类和结构体在创建实例时必须为所有存储的属性设置适当的初始值。存储属性的值不能处于未知状态。
您可以在构造函数中为存储的属性分配初始值,也可以在定义属性时设置默认值。以下小节详细描述这两种方法。
注意
当您为存储的属性设置默认值或在构造函数中为其赋值时,它们的值将被直接设置,并且不会触发属性观察器。
构造器创建特定类型的新实例时调用构造函数。它最简单的形式类似于不带任何参数的实例方法,以关键字init 命名:
初始化(){
//这里执行构建过程
}下面的示例定义了一个结构体Fahrenheit 用于存储华氏温度。它有一个Double 类型的存储属性温度:
结构华氏度{
变量温度: 双
初始化(){
温度=32.0
}
}
var f=华氏度()
print("默认温度为(f.Temperature)华氏度")
//Print "默认温度为32.0 Fahrenheit" 该结构体定义了一个不带参数的构造函数init,并将存储的属性温度的值初始化为32.0(华氏度水的冰点)。
默认属性值正如前面提到的,您可以在构造函数中为存储的属性设置初始值。同样,您可以在声明属性时设置属性的默认值。
注意
如果属性始终使用相同的初始值,则为其设置默认值比每次在构造函数中分配它更好。两种方法的效果是一样的,只不过使用默认值使得属性的初始化和声明结合得更加紧密。使用默认值可以让你的构造函数更简单、更清晰,并且可以通过默认值自动推导属性的类型。同时,它还允许您充分利用默认构造函数和构造函数继承等功能,这些功能将在后续章节中讨论。到达。
在定义结构体Fahrenheit 时,可以使用更简单的方法为属性温度设置默认值:
结构华氏度{
无功温度=32.0
}自定义构造过程可以通过输入可选类型的参数和属性来自定义构建过程,也可以在构建过程中修改常量属性。这些都会在后面的章节中提到。
构造参数自定义构造过程时,可以在定义中提供构造参数,并指定所需值的类型和名称。构造函数参数的功能和语法与函数和方法参数相同。
以下示例定义了一个包含摄氏度温度的结构体。它定义了两个不同的构造函数:init(fromFahrenheit:) 和init(fromKelvin:),这两个构造函数都通过接受不同温标的温度值来创建新实例:
结构摄氏度{
var 温度InCelsius: 双精度
init(来自华氏度fahrenheit: 双) {
摄氏温度=(华氏度- 32.0)/1.8
}
初始化(来自开尔文kelvin: 双){
温度(摄氏度)=开尔文- 273.15
}
}
让沸腾点水=摄氏度(来自华氏: 212.0)
//沸点水.温度为100.0
让冷冻水点=摄氏度(来自Kelvin: 273.15)
//freezePointOfWater.temporalInCelsius is 0.0 第一个构造函数有一个构造参数,其外部名称为fromFahrenheit,其内部名称为fahrenheit;第二个构造函数也有一个构造参数,它的外部名称是fromKelvin,它的内部名称是kelvin。两个构造函数都将唯一参数值转换为摄氏温度值并将其保存在属性TemperatureInCelsius 中。
参数的内部名称和外部名称与函数和方法参数一样,构造参数也有构造函数内部使用的参数名称和调用构造函数时使用的外部参数名称。
但是,构造函数不像函数和方法那样在括号前有可识别的名称。因此,在调用构造函数时,应该调用的构造函数主要由构造函数中的参数名称和类型来确定。因为参数如此重要,如果在定义构造函数时不提供参数的外部名称,Swift 会自动为构造函数的每个参数生成一个与内部名称相同的外部名称。
下面的示例定义了一个结构体Color,其中包含三个常量:红色、绿色和蓝色。这些属性可以存储0.0到1.0之间的值,表示颜色中红、绿、蓝成分的数量。
Color 提供了一个构造函数,其中包含三个Double 类型的构造参数。 Color还可以提供第二个构造函数,它只包含一个名为white的Double类型的参数,用于为上面的三个构造参数赋值相同的值。
结构颜色{
让红、绿、蓝:双
初始化(红色:双,绿色:双,蓝色:双){
self.red=红色
self.green=绿色
self.blue=蓝色
}
初始化(white: 双){
红色=白色
绿色=白色
蓝色=白色
}
两个构造函数都可用于创建新的Color 实例。您需要为构造函数的每个外部参数传递一个值:
让洋红色=颜色(红色: 1.0,绿色: 0.0,蓝色: 1.0)
let halfGray=Color(white: 0.5) 请注意,如果不通过外部参数名称传递值,则无法调用此构造函数。每当构造函数定义外部参数名称时,您都必须使用它;省略它会导致编译错误:
让veryGreen=颜色(0.0, 1.0, 0.0)
//报告编译时错误,外部名称不带外部名的构造器参数为必填项。如果不想为构造函数的某个参数提供外部名称,可以使用下划线(_)显式描述其外部名称,从而重写上面所说的默认行为
以下是先前摄氏示例的扩展。与之前相比,增加了一个Double类型参数的构造函数,并且其外部名称被_替换:
结构摄氏度{
var 温度InCelsius: 双精度
init(来自华氏度fahrenheit: 双) {
摄氏温度=(华氏度- 32.0)/1.8
}
初始化(来自开尔文kelvin: 双){
温度(摄氏度)=开尔文- 273.15
}
初始化(_celsius: 双){
温度单位摄氏度=摄氏度
}
}
让身体温度=摄氏度(37.0)
//bodyTemperature.TemperatureInCelsius 使用摄氏度(37.0) 表示37.0,意图明确,并且不需要外部参数名称。因此,适合使用init(_celsius: Double)这样的构造函数,这样可以通过提供Double类型的参数值来调用该构造函数,而无需添加外部名称。
可选属性类型如果你的自定义类型包含一个逻辑上允许空值的存储属性,要么是因为它在初始化期间不能被赋值,要么是因为它可以在稍后的时间点被赋值为空值,那么你需要为它定义作为可选类型。可选属性会自动初始化为nil,表明该属性在初始化期间被有意设置为空。
以下示例定义了SurveyQuestion 类,其中包含可选的字符串属性响应:
类调查问题{
var text: 字符串
var response: 字符串?
初始化(text:字符串){
self.text=文本
}
函数询问(){
打印(文本)
}
}
let CheeseQuestion=SurveyQuestion(text: "你喜欢奶酪吗?")
奶酪问题.ask()
//print "你喜欢奶酪吗?"
CheeseQuestion.response="是的,我确实喜欢奶酪。"在回答之前无法确定调查问题的答案,因此我们将属性response声明为String?类型,或可选的字符串类型。当SurveyQuestion 被实例化时,它会自动被赋值为nil,表示这个字符串还没有值。
构造过程中常量属性的修改您可以在构造过程中的任何时刻为常量属性赋值,只要它在构造过程结束时是确定的值即可。常量属性一旦被赋值,就永远无法更改。
注意
对于类的实例,其常量属性只能在定义它的类的构造过程中修改;它们不能在子类中修改。
您可以修改上面的SurveyQuestion示例,将变量属性文本替换为常量属性,表示创建SurveyQuestion实例后不会修改问题内容文本。虽然text 属性现在是一个常量,但我们仍然可以在类的构造函数中设置它的值:
类调查问题{
让text: 字符串
var response: 字符串?
初始化(text:字符串){
self.text=文本
}
函数询问(){
打印(文本)
}
}
let beetsQuestion=SurveyQuestion(text: "甜菜怎么样?")
甜菜问题.ask()
//print "甜菜怎么样?"
beetsQuestion.response="我也喜欢甜菜。 (但不是奶酪。)"默认构造器如果一个结构体或类的所有属性都有默认值,并且没有自定义构造函数,Swift 会给这些结构体或类提供一个默认的初始化器(defaultinitializers)。这个默认构造函数将简单地创建一个实例,并将所有属性值设置为其默认值。
在上面的例子中,创建了一个ShoppingListItem类,它封装了购物清单中商品的属性:名称、数量和购买状态:
类ShoppingListItem {
var name: 字符串?
变量数量=1
购买的变量=false
}
var item=ShoppingListItem() 由于ShoppingListItem类中的所有属性都有默认值,并且是没有父类的基类,所以它会自动获取一个默认构造函数,可以为所有属性设置默认值(虽然有代码中没有显式值为name 属性设置默认值,但由于name 是可选字符串类型,因此默认为nil)。在上面的示例中,默认构造函数用于创建ShoppingListItem 类的实例(使用ShoppingListItem() 形式的构造函数语法)并分配给变量item。
结构体的逐一成员构造器除了上面提到的默认初始值设定项之外,如果结构不提供自定义初始值设定项,即使结构的存储属性没有默认值,它们也会自动获取逐个成员初始值设定项。
逐个成员初始化程序是初始化结构的新实例的成员属性的快速方法。当我们调用逐个成员构造函数时,我们传递与成员属性名称相同的参数名称,以完成成员属性的初始赋值。
下面的示例定义了一个结构体Size,它包含两个属性width和height。 Swift 可以根据这两个属性的初始赋值0.0 自动推断出它们的类型是Double。
结构大小{
变量宽度=0.0,高度=0.0
}
lettwoByTwo=Size(width: 2.0, height: 2.0)值类型的构造器代理构造函数可以通过调用其他构造函数来完成实例的部分构建过程。这个过程称为构造函数委托,它减少了多个构造函数之间的代码重复。
值类型和类类型之间构造函数代理的实现规则和形式有所不同。值类型(结构体和枚举类型)不支持继承,因此构造函数委托的过程比较简单,因为它们只能委托给自己的其他构造函数。另一方面,类可以从其他类继承(请参阅继承),这意味着类有责任确保其所有继承的存储属性在构造期间正确初始化。这些职责将在后续的类继承和构造章节中介绍。
对于值类型,您可以使用self.init 来引用自定义初始值设定项中相同类型的其他初始值设定项。并且只能在构造函数中调用self.init 。
如果为值类型定义自定义初始值设定项,您将无法访问默认初始值设定项(或结构体情况下的逐个成员初始值设定项)。此限制阻止您向值类型添加额外且非常复杂的初始值设定项,并且有人仍然错误地使用自动生成的初始值设定项。
注意
如果您希望使用默认初始值设定项、逐个成员初始值设定项和您自己的自定义初始值设定项来创建实例,则可以在扩展中而不是在值类型的原始定义中编写自定义初始值设定项。中间。要了解更多信息,请查看扩展章节。
下面的例子将定义一个结构体Rect 来表示一个几何矩形。此示例需要两个辅助结构Size 和Point,每个结构为其所有属性提供初始值0.0。
结构大小{
变量宽度=0.0,高度=0.0
}
结构点{
变量x=0.0,y=0.0
可以通过以下三种方式创建Rect 的实例: —— 使用初始化为默认值的origin 和size 属性进行初始化;为初始化提供指定的原点和大小实例;为初始化提供指定的中心和大小。在下面的Rect 结构定义中,我们为这三个方法提供了三个自定义构造函数:
结构矩形{
var 原点=点()
变量大小=Size()
初始化(){}
init(origin: 点, size: 大小) {
self.origin=起源
self.size=大小
}
init(center: 点, size: 大小) {
让originX=center.x - (size.width/2)
让originY=center.y - (size.height/2)
self.init(origin: 点(x: originX, y: originY), size: 大小)
}
}第一个Rect 构造函数init() 在功能上与没有自定义构造函数时自动获取的默认构造函数相同。这个构造函数是一个空函数,由一对大括号{}表示,它不执行任何构造过程。调用此构造函数将返回一个Rect 实例,其origin 和size 属性在定义时使用Point(x: 0.0, y: 0.0) 和Size(width: 0.0, height: 0.0) 的默认值:
让基本矩形=矩形()
//basicRect的原点是(0.0, 0.0),大小是(0.0, 0.0)。第二个Rect 构造函数init(origin:size:) 在功能上与没有自定义构造函数时通过结构体获取的逐成员构造函数相同。相同的。该构造函数只是将origin 和size 参数值分配给相应的存储属性:
让originRect=矩形(origin: 点(x: 2.0,y: 2.0),
size: 尺寸(宽度: 5.0,高度: 5.0))
//originRect的原点为(2.0, 2.0),大小为(5.0, 5.0)。第三个Rect 构造函数init(center:size:) 稍微复杂一些。它首先通过center和size的值计算出原点坐标,然后调用(或代理)init(origin:size:)构造函数将新的origin和size值赋给相应的属性:
让centerRect=矩形(center:点(x: 4.0,y: 4.0),
size: 尺寸(宽度: 3.0,高度: 3.0))
//centerRect的原点为(2.5, 2.5),大小为(3.0, 3.0)。构造函数init(center:size:)可以直接将origin和size的新值赋给对应的属性。但是,使用恰好提供相关功能的现有构造函数会更方便,并且构造函数init(center:size:) 的意图会更清晰。
注意
如果您想以其他方式实现此示例,而不需要自己定义init() 和init(origin:size:),请参考扩展
类的继承和构造过程类中所有存储的属性——,包括从父类继承的所有属性——,在构造过程中必须设置为初始值。
Swift 为类类型提供了两个初始化器,以确保实例中所有存储的属性都能获取初始值。它们是指定初始化器和便利初始化器。
指定构造器和便利构造器指定的构造函数是类中的主构造函数。指定的初始化器将初始化类中提供的所有属性,并在父类链上调用父类的构造函数来实现父类的初始化。
每个类必须至少有一个指定的构造函数。在某些情况下,许多类通过从父类继承指定的构造函数来满足此条件。详细内容请参考后续章节构造函数的自动继承。
便利初始化器是类中次要的辅助初始化器。您可以定义一个方便的初始值设定项来调用同一类中的指定初始值设定项,并为其参数提供默认值。您还可以定义便利的初始值设定项来创建用于特殊目的或特定输入值的实例。
您应该只在必要时为类提供便利的初始值设定项。例如,在某些情况下,使用便利初始化器快速调用指定的构造函数可以节省更多的开发时间,并使类的构造过程更加清晰。
指定构造器和便利构造器的语法类的指定初始化器的编写方式与值类型的简单初始化器相同:
初始化(参数){
声明
便利初始化器也以相同的风格编写,但便利关键字需要放在init 关键字之前,并使用空格将它们分开:
方便init(参数) {
声明
}类的构造器代理规则为了简化指定初始化器和便利初始化器之间的调用关系,Swift 采用以下三个规则来限制构造函数之间的代理调用:
规则1
指定的初始值设定项必须调用其直接超类的指定初始值设定项。规则2
便利初始化器必须调用其类中定义的其他初始化器。规则3
便利初始化器最终必须导致调用指定的初始化器。更方便的记忆方法是:
* 指定初始化器必须始终向上委托
* 便利初始化器必须始终水平委托。这些规则如下图所示:
如图所示,父类包含一个指定初始化器和两个便利初始化器。一个便利初始值设定项调用另一个便利初始值设定项,后者又调用唯一指定的初始值设定项。这满足上述规则2和3。该父类没有自己的父类,因此规则1 不适用。
该子类包含两个指定的初始值设定项和一个便利初始值设定项。便利初始化器必须调用两个指定初始化器中的一个,因为它只能调用同一类中的其他初始化器。这满足上述规则2和3。两个指定初始化器必须调用父类中唯一的指定初始化器,这满足规则1。
注意
这些规则不影响类实例的创建方式。上面显示的任何构造函数都可用于创建完全初始化的实例。这些规则仅影响类定义的实现方式。
下图显示了涉及四个类的更复杂的类层次结构。它演示了指定构造函数如何充当类层次结构中的“管道”,从而简化构造函数链中类之间的关系。
两段式构造过程Swift 中类的构建过程由两个阶段组成。在第一阶段,每个存储的属性都由引入它的类分配一个初始值。当确定每个存储属性的初始值时,第二阶段开始,这使每个类有机会在新实例可供使用之前进一步自定义其存储属性。
使用两阶段构建过程使构建过程更加安全,同时在整个类层次结构中为每个类提供了完全的灵活性。两阶段构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另一个构造函数意外地分配不同的值。
注意
Swift 的两阶段构建过程与Objective-C 中的构建过程类似。主要区别在于,在第1 阶段,Objective-C 为每个属性分配0 值或空值(例如0 或nil)。 Swift 的构造过程更加灵活,允许您设置自定义初始值并处理某些属性不能将0 或nil 作为合法默认值的情况。
Swift 编译器执行四项有效的安全检查,以确保两阶段构建过程顺利完成:
安全检查 1指定的构造函数必须确保其所在类引入的所有属性都必须经过初始化,然后才能将其他构造任务委托给父类中的构造函数。
如上所述,在确定对象的所有存储属性之前,无法完全初始化对象的内存。为了满足这个规则,指定的初始化器必须确保
它所在类引入的属性在它往上代理之前先完成初始化。安全检查 2指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。安全检查 3便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。安全检查 4构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。 类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。 以下是两段式构造过程中基于上述安全检查的构造流程展示:阶段 1- 某个指定构造器或便利构造器被调用。 - 完成新实例内存的分配,但此时内存还没有被初始化。 - 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。 - 指定构造器将调用父类的构造器,完成父类属性的初始化。 - 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。 - 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成阶段 2- 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。 - 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。下图展示了在假定的子类和父类之间的构造阶段 1: 在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。 如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构造过程。 父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要初始化,也就无需继续向上代理。 一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段 1 完成。 以下展示了相同构造过程的阶段 2: 父类中的指定构造器现在有机会进一步来定制实例(尽管这不是必须的)。 一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的定制操作(这也不是必须的)。 最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。构造器的继承和重写跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。 注意 父类的构造器仅会在安全和适当的情况下被继承。具体内容请参考后续章节构造器的自动继承。 假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。 当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上override修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上override修饰符,具体内容请参考默认构造器。 正如重写属性,方法或者是下标,override修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否正确。 注意 当你重写一个父类的指定构造器时,你总是需要写override修饰符,即使你的子类将父类的指定构造器重写为了便利构造器。 相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文类的构造器代理规则有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加override前缀。 在下面的例子中定义了一个叫Vehicle的基类。基类中声明了一个存储型属性numberOfWheels,它是值为0的Int类型的存储型属性。numberOfWheels属性用于创建名为descrpiption的String类型的计算型属性: class Vehicle { var numberOfWheels = 0 var description: String { return "(numberOfWheels) wheel(s)" } }Vehicle类只为存储型属性提供默认值,而不自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考默认构造器。自动获得的默认构造器总会是类中的指定构造器,它可以用于创建numberOfWheels为0的Vehicle实例: 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,子类可以将父类的指定构造器实现为便利构造器。指定构造器和便利构造器实践接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。这个例子定义了包含三个类Food、RecipeIngredient以及ShoppingListItem的类层次结构,并将演示它们的构造器是如何相互作用的。 类层次中的基类是Food,它是一个简单的用来封装食物名字的类。Food类引入了一个叫做name的String类型的属性,并且提供了两个构造器来创建Food实例: class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } }下图中展示了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的子类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类的构造器链: RecipeIngredient类拥有一个指定构造器init(name: String, quantity: Int),它可以用来填充RecipeIngredient实例的所有属性值。这个构造器一开始先将传入的quantity参数赋值给quantity属性,这个属性也是唯一在RecipeIngredient中新引入的属性。随后,构造器向上代理到父类Food的init(name: String)。这个过程满足两段式构造过程中的安全检查 1。 RecipeIngredient还定义了一个便利构造器init(name: String),它只通过name来创建RecipeIngredient的实例。这个便利构造器假设任意RecipeIngredient实例的quantity为1,所以不需要显式指明数量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个quantity为1的RecipeIngredient实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为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,它的默认值是false。ShoppingListItem还添加了一个计算型属性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来表明构造成功。 例如,实现针对数字类型转换的可失败构造器。确保数字类型之间的转换能保持精确的值,使用这个 init(exactly:) 构造器。如果类型转换不能保持值不变,则这个构造器构造失败。 let wholeNumber: Double = 12345.0 let pi = 3.14159 if let valueMaintained = Int(exactly: wholeNumber) { print("(wholeNumber) conversion to Int maintains value") } // 打印 "12345.0 conversion to Int maintains value" let valueChanged = Int(exactly: pi) // valueChanged 是 Int? 类型, 不是 Int 类型 if valueChanged == nil { print("(pi) conversion to Int does not maintain value") } // 打印 "3.14159 conversion to Int does not maintain value"下例中,定义了一个名为Animal的结构体,其中有一个名为species的String类型的常量属性。同时该结构体还定义了一个接受一个名为species的String类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串,则构造失败。否则,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"如果你给该可失败构造器传入一个空字符串作为其参数,则会导致构造失败: 注意 空字符串(如"",而不是"Giraffe")和一个值为nil的可选类型的字符串是两个完全不同的概念。上例中的空字符串("")其实是一个有效的,非可选类型的字符串。这里我们之所以让Animal的可失败构造器构造失败,只是因为对于Animal这个类的species属性来说,它更适合有一个具体的值,而不是空字符串。枚举类型的可失败构造器你可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。 下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(Kelvin,Celsius,和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."构造失败的传递类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。 无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。 注意 可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。 下面这个例子,定义了一个名为CartItem的Product类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为quantity的常量存储型属性,并确保该属性的值至少为1: class Product { let name: String init?(name: String) { if name.isEmpty { return nil } self.name = name } } class CartItem: Product { let quantity: Int init?(name: String, quantity: Int) { if quantity< 1 { return nil } self.quantity = quantity super.init(name: name) } }CartItem 可失败构造器首先验证接收的 quantity 值是否大于等于 1 。倘若 quantity 值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码。同样地,Product 的可失败构造器首先检查 name 值,假如 name 值为空字符串,则构造器立即执行失败。 如果你通过传入一个非空字符串 name 以及一个值大于等于 1 的 quantity 来创建一个 CartItem 实例,那么构造方法能够成功被执行: if let twoSocks = CartItem(name: "sock", quantity: 2) { print("Item: (twoSocks.name), quantity: (twoSocks.quantity)") } // 打印 "Item: sock, quantity: 2"倘若你以一个值为 0 的 quantity 来创建一个 CartItem 实例,那么将导致 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,它构建了西洋跳棋游戏的棋盘: 西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行。为了呈现这副游戏棋盘,Checkerboard结构体定义了一个属性boardColors,它是一个包含100个Bool值的数组。在数组中,值为true的元素表示一个黑格,值为false的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。 boardColor数组是通过一个闭包来初始化并设置颜色值的: 每当一个新的Checkerboard实例被创建时,赋值闭包会被执行,boardColors的默认值会被计算出来并返回。上面例子中描述的闭包将计算出棋盘中每个格子对应的颜色,并将这些值保存到一个临时数组temporaryBoard中,最后在构建完成时将此数组作为闭包返回值返回。这个返回的数组会保存到boardColors中,并可以通过工具函数squareIsBlackAtRow来查询: let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) // 打印 "true" print(board.squareIsBlackAtRow(7, column: 7))【全面解析 iOS 开发:Swift 编程入门教程】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于决定学习IOS开发了,正好看到这个教程!
有10位网友表示赞同!
我一直想学Swift,现在开始做个目标吧。
有17位网友表示赞同!
之前用过Android,这次来试试iOS。
有6位网友表示赞同!
这门课程难度适中吗?我初学者可以看么?
有18位网友表示赞同!
什么时候更新这个教程? 想看看有没有最新的内容!
有13位网友表示赞同!
学习Swift有什么途径除了看教程?
有11位网友表示赞同!
希望能有更详细的练习题,这样能更好理解。
有17位网友表示赞同!
看到教程里提到了苹果官方文档,是不是很权威?
有20位网友表示赞同!
iOS真机体验一定比模拟器好很多吧!
有8位网友表示赞同!
学完Swift之后有什么实际应用场景呢?
有14位网友表示赞同!
这门教程包含哪些内容? 是全面讲解还是重点突破?
有7位网友表示赞同!
学习Swift需要花多少时间才能入门?
有11位网友表示赞同!
这个教程适合想转行做iOS开发的吗?
有10位网友表示赞同!
有没有分享一些好用的iOS开发学习资源?
有12位网友表示赞同!
学完Swift以后,还有其他技术栈需要学习吗?
有8位网友表示赞同!
想知道学习Swift的入门门槛是什么?
有10位网友表示赞同!
现在iOSAPP市场竞争激烈吗?
有18位网友表示赞同!
这个教程适合想做游戏开发的吗?
有11位网友表示赞同!
希望能找到一些相关的交流社区,跟其他学长学姐讨论问题!
有6位网友表示赞同!
学习Swift很有用吧,未来对就业更有优势?
有14位网友表示赞同!