该页面包含内容:
存储属性的初始赋值自定义构造过程默认初始值设定项值类型的构造函数代理类的继承和构造过程可失败的初始值设定项必需的初始值设定项通过闭包或函数为属性设置默认值构造过程是使用类、结构体或准备过程完成的在枚举类型的实例之前。此过程必须在新实例可用之前执行。具体操作包括设置实例中每个存储属性的初始值以及执行其他必要的设置或初始化工作。
构造过程是通过定义初始值设定项来实现的,可以将其视为用于创建特定类型的新实例的特殊方法。与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 }【全面解析 iOS 开发:Swift 编程入门教程】相关文章:
用户评论
终于决定学习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位网友表示赞同!