深入探讨 Swift 4.0:编程语言系列教程第五篇

更新:10-28 名人轶事 我要投稿 纠错 投诉

今天给各位分享深入探讨 Swift 4.0:编程语言系列教程第五篇的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

Swift 中的类可以调用和访问超类的方法、属性和下标,并且可以重写这些方法、属性和下标以重新定义或修改它们的行为。 Swift 会通过判断重写的定义在超类中是否有对应的定义来确保重写的正确性。

类可以向继承的属性添加属性观察器,并在属性值更改时接收通知。属性观察器可以添加到任何属性,无论它最初定义为存储属性还是计算属性。

124.定义一个基类

任何不从其他类继承的类称为基类。

注意:Swift 类不继承自公共基类。如果您定义的类没有超类,它将自动成为基类。

以下示例定义了一个基类Vehicle。该基类定义了一个存储属性currentSpeed,其默认值为0.0(推断为浮点属性)。只读计算字符串属性描述使用currentSpeed 属性来创建汽车的描述。

Vehicle 基类还定义了makeNoise 方法。这个方法实际上并没有对基类实例做任何事情,而是稍后会被子类自定义:

类车辆{

var 当前速度=0.0

var description: 字符串{

返回“以每小时(当前速度)英里行驶”

}

函数makeNoise() {

//什么都不做- 任意车辆不一定会发出噪音

}

}

使用初始化语法创建一个Vehicle 实例,写入类型名称后跟括号:

让someVehicle=车辆()

一个新的Vehicle 实例已创建,您可以访问其描述属性来打印有关汽车当前速度的人类可读信息:

print("Vehicle: (someVehicle.description)")

//Vehicle: 以每小时0.0 英里的速度行驶

Vehicle 类定义了任何汽车的通用属性,但其本身几乎没有用处。为了使其更有用,您需要充实它以描述更具体的汽车。

子类化

子类化充当基于现有类的新类。子类继承现有类的功能,然后您可以对其进行改进。您还可以向子类添加新功能。

为了表明子类有超类,子类名写在超类名之前,用冒号分隔:

类SomeSubclass: SomeSuperclass {

//子类定义放在这里

}

以下示例定义了一个子类Bicycle,其超类Vehicle:

类别Bicycle: 车辆{

var hasBasket=false

}

新的Bicycle 类自动获取Vehicle 的所有功能,例如currentSpeed 和描述属性及其makeNoise() 方法。

除了继承的功能之外,Bicycle 类还定义了一个新的存储属性hasBasket,其默认值为false(属性类型推断为Boolean)。

默认情况下,您创建的任何Bicycle 实例都不会有篮子。您可以在创建Bicycle 实例后将hasBasket 属性设置为true:

让自行车=自行车()

自行车.hasBasket=true

可以修改继承的currentSpeed属性,查询实例继承的description属性:

自行车.currentSpeed=15.0

print("Bicycle: (bicycle.description)")

//Bicycle: 以每小时15.0 英里的速度行驶

子类本身也可以被子类化。以下示例创建一辆两座自行车作为Bicycle : 的子类

Tandem: 自行车类{

var 当前乘客数量=0

}

Tandem 继承了Bicycle 的所有属性和方法,Bicycle 又继承了Vehicle 的所有属性和方法。 Tandem 子类还添加了一个新的存储属性currentNumberOfPassengers,默认值为0。

如果您创建Tandem 实例,则可以使用其任何新的和继承的属性,以及查询从Vehicle : 继承的只读描述属性

让串联=串联()

tandem.hasBasket=true

tandem.currentNumberOfPassengers=2

tandem.currentSpeed=22.0

print("Tandem: (tandem.description)")

//Tandem: 以每小时22.0 英里的速度行驶

改写

子类可以自定义其实现并从超类继承实例方法、类型方法、实例属性、类型属性或下标。

要覆盖另一个继承的功能,请在需要覆盖的定义之前写入override 关键字。为了澄清,您想要提供覆盖,但无法提供错误的匹配定义。意外重写会导致不良行为。如果重写时不包含override关键字,那么代码编译时会报错。

override 关键字提示Swift 编译器检查超类是否具有匹配的声明。这个判断保证了你的重新定义是正确的。

访问超类方法、属性和下标

当您为子类提供方法、属性和下标的重写时,使用超类实现作为子类重写的一部分非常有用。例如,您可以改进现有实现的行为,或者将更改的值存储在现有的继承变量中。

在适当的情况下,您可以使用super 前缀来访问方法、属性或下标的超类版本:

重写的方法someMethod()可以在实现中通过super.someMethod()调用超类的someMethod()。

重写的属性someProperty 可以通过重写的setter 和getter 实现中的super.someProperty 访问超类的someProperty。

被重写的下标someIndex可以通过super[someIndex]访问被重写下标实现中的超类下标super[someIndex]。

重写方法

您可以重写实例或类型方法以在子类中提供自定义方法实现。

下面的示例定义了Vehicle的子类Train,它重写了从Vehicle:继承的makeNoise()方法

类Train: 车辆{

覆盖函数makeNoise() {

打印("咕咕")

}

}

如果创建一个新的Train 实例并调用其makeNoise() 方法,您可以看到该方法的Train 子类版本称为:

让火车=火车()

火车.makeNoise()

//打印“咕咕”

覆盖属性

您可以重写继承的实例或类型属性来为该属性提供自定义getter 和setter,或者添加属性观察器以确保重写属性可以观察到潜在的属性值更改。

重写属性Getter 和Setter

您可以提供自定义getter(以及setter,如果适用)来覆盖任何继承的属性,无论继承的属性是存储属性还是计算属性。子类不知道继承的属性是计算的还是存储的,它只知道继承的属性有名称和类型。您应该声明重写属性的名称和类型,以确保编译器确定您的重写属性与超类属性具有相同的名称和类型。

在子类中重写时,您可以提供setter 和getter,将继承的只读属性转换为读写属性。但是,您不能将继承的读写属性更改为只读属性。

注意:如果您提供setter 作为覆盖的一部分,则还必须提供getter。如果你不想在getter中改变继承属性的值,你可以简单地通过返回super.someProperty来获取该值,someProperty就是你想要覆盖的属性名称。

以下示例定义了一个新类Car,它是Vehicle 的子类。 Car类引入了一个新的存储属性gear,默认值为1。Car类还重写了继承自Vehicle的description属性,提供了包含当前gear的自定义描述:

类Car: 车辆{

无功齿轮=1

覆盖var description: 字符串{

return super.description + " in gear (gear)"

}

}

描述属性覆盖首先调用super.description,这会放回Vehicle 类的描述属性。 Car 类的描述版本在描述后添加了一些额外的文本,以提供有关当前档位的信息。

如果创建一个新的Car 实例并设置其gear 和currentSpeed 属性,您可以看到它返回自定义描述:

让汽车=汽车()

car.currentSpeed=25.0

汽车档位=3

print("Car: (car.description)")

//Car: 以25.0 英里/小时的速度行驶,档位3

压倒一切的财产观察者

您可以使用属性重写将观察者添加到继承的属性中。这可确保您在继承的属性发生更改时收到通知,无论该属性最初是如何实现的。

注意:您不能将属性观察器添加到继承的常量存储属性或只读计算属性。这些属性的值无法设置,因此重写时不适合提供willSet或didSet。

请注意,您不能为同一属性同时提供重写的setter 和重写的属性观察器。如果您想要观察属性值的变化,并且您已经为该属性提供了自定义setter,则可以观察自定义setter 中任何值的变化。

下面的示例定义了一个新类AutomaticCar,它是Car的子类。 AutomaticCar 类代表自动驾驶的汽车。它将根据当前速度自动选择合适的档位:

类AutomaticCar:汽车{

覆盖var currentSpeed: Double {

没有设置{

齿轮=Int(当前速度/10.0) + 1

}

}

}

一旦设置了AutomaticCar实例的currentSpeed属性,该属性的didSet观察者就会为新的速度设置适当的档位。特别是,该属性观察到所选档位等于新的currentSpeed 值除以10,然后转换为最接近的整数加1。35.0 的速度产生4: 的档位

让自动=自动汽车()

自动.currentSpeed=35.0

print("AutomaticCar: (automatic.description)")

//AutomaticCar: 以35.0 英里/小时的速度在4 档行驶

防止重写

您可以通过将方法、属性或下标标记为Final 来防止其被覆盖。您可以通过在方法、属性或带下标的关键字(例如final var、final func、final class func 和final 下标)之前写入final 标识符来实现此目的。

任何重写子类中的final方法、属性或下标的尝试都会触发编译器错误。添加到扩展中的类的方法、属性或下标也可以在定义扩展时标记为Final。

可以在类定义之前标记final关键字,以表明整个类不能被覆盖。任何尝试子类化此类都会导致编译器错误。

初始化

初始化是使用类、结构或枚举之前的准备过程。这个过程涉及为每个存储的属性设置初始值,并在被其他实例使用之前执行其他设置和初始化工作。

这个过程是通过定义初始化方法来完成的,这些方法很像用于创建特定类型实例的特殊函数。与Objective-C 初始化方法不同,Swift 初始化方法不返回值。他们的主要任务是确保类型的实例在使用前正确初始化。

类类型的实例还可以实现去初始化方法,该方法在类被销毁之前执行清理工作。

设置存储属性的初始值

创建类或结构实例时,必须将其存储的属性设置为适当的初始值。无法取消设置存储的属性。

您可以在初始化方法中为存储的属性设置初始值,或者指定默认属性值作为属性定义的一部分。这些操作描述如下。

注意:当您为存储的属性指定默认值,或在初始化方法中为其设置初始值时,设置是直接的,无需调用任何属性观察器。

初始化方法

初始化方法用于创建特定类型的实例。在最简单的形式中,初始化方法很像没有参数的实例方法,用关键字init 编写:

初始化(){

//在这里执行一些初始化

}

下面的示例定义了一个新的结构体Fahrenheit,它存储华氏温度表达式。 Fahrenheit结构体有一个存储属性温度,类型为

双:

结构华氏度{

变量温度: 双

初始化(){

温度=32.0

}

}

var f=华氏度()

print("默认温度为(f.Temperature)华氏度")

//print "默认温度为32.0 华氏度"

该结构体定义了一个单独的初始化方法init,不带参数,初始温度值为32.0(华氏温度的冰点)。

默认属性值

您可以在初始化方法中设置存储属性的值,如上所示。或者,您可以指定默认属性值作为属性定义的一部分。

注意:如果属性始终具有相同的初始值,请提供默认值,而不是在初始化期间设置值。结果是相同的,但默认值将属性的初始化与其定义更紧密地联系在一起。它更短、更清晰。确保您可以根据属性的默认值推断其类型。默认值使您可以更轻松地利用默认初始值设定项和初始值设定项继承,这将在本章后面介绍。

您可以将上述结构写成简单的华氏度形式,并在声明属性时为其提供默认值:

结构华氏度{

无功温度=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.TemperatureInCelsius 为0.0

第一个初始化方法只有一个参数,该参数有一个参数标签fromFahrenheit 和一个参数名称fahrenheit。第二个初始化方法也只有一个参数,该参数有一个标签fromKelvin 和一个参数名称kelvin。两种初始化方法都是将参数转换为对应的摄氏温度值,并将其存储在属性TemperatureInCelsius中。

参数名称和参数标签

与函数和方法参数一样,初始化参数可以使用参数名称和参数标签。

但是,初始化方法不能像函数和方法一样在括号前有标识函数名称。因此,初始化方法的参数名称和参数类型对于区分应该调用哪个初始化方法起着非常重要的作用。因此,如果您无法提供参数标签,Swift 将自动提供一个。

以下示例定义了具有三个常量属性红色、绿色和蓝色的结构Color。这些属性存储0.0到1.0之间的值来表示颜色中红、绿、蓝的数量。

Color 为三基色提供了一个初始化方法,具有三个Double 类型的命名参数。 Color还提供了带有白色参数的初始化方法,用于为三基色提供相同的值。

结构颜色{

让红、绿、蓝:双

初始化(红色:双,绿色:双,蓝色:双){

self.red=红色

self.green=绿色

self.blue=蓝色

}

初始化(white: 双){

红色=白色

绿色=白色

蓝色=白色

}

}

两种初始化方法都是通过给每个初始化参数赋值来创建新的Color实例:

让洋红色=颜色(红色: 1.0,绿色: 0.0,蓝色: 1.0)

让halfGray=颜色(white: 0.5)

请注意,如果没有参数标签,则无法调用这些初始化方法。如果已定义,则必须始终使用参数标签。忽略它们会导致编译错误:

让veryGreen=颜色(0.0, 1.0, 0.0)

//这会报告编译错误- 需要参数标签

没有参数标签的初始化参数

如果您不想使用参数标签,请使用下划线(_) 代替参数的显式参数标签来覆盖默认行为。

下面是早期摄氏温度示例的扩展版本,使用附加的初始化方法从浮点值(已经是: 摄氏度)创建新的摄氏温度实例。

结构摄氏度{

var 温度InCelsius: 双精度

init(来自华氏度fahrenheit: 双) {

摄氏温度=(华氏度- 32.0)/1.8

}

初始化(来自开尔文kelvin: 双){

温度(摄氏度)=开尔文- 273.15

}

初始化(_ celsius: 双){

温度单位摄氏度=摄氏度

}

}

让身体温度=摄氏度(37.0)

//bodyTemperature.TemperatureInCelsius 为37.0

调用Celsius(37.0)初始化方法的目的很明确,不需要使用参数标签。因此,编写初始化方法init(_ celsius: Double) 是合适的,可以使用未命名的浮点值来调用该方法。

可选属性类型

如果您的自定义类型具有不允许任何值的存储属性(可能是因为在初始化期间无法设置其值,或者因为它在某些时候不允许任何值),请将该属性定义为可选类型。可选属性会自动初始化为nil,表示该属性有意被初始化为“无值”。

以下示例定义了一个类SurveyQuestion,具有可选的字符串属性response:

类调查问题{

var text: 字符串

var response: 字符串?

初始化(text:字符串){

self.text=文本

}

函数询问(){

打印(文本)

}

}

let CheeseQuestion=SurveyQuestion(text: "你喜欢奶酪吗?")

奶酪问题.ask()

//print "你喜欢奶酪吗?"

CheeseQuestion.response="是的,我确实喜欢奶酪。"

调查问题的答复只有在您提出后才会知道。所有响应属性都声明为字符串吗?类型或“可选字符串”。当一个新的SurveyQuestion实例被初始化时,它会被自动分配一个默认值nil,这意味着“还没有字符串”。

在初始化期间访问常量属性

在初始化期间,您可以随时为常量属性赋值,只要在初始化完成时将其设置为某个值即可。常量属性一旦指定了值,以后就无法修改。

注意:对于类实例,类引入的常量属性只能在初始化期间由该类更改。它不能被子类修改。

您可以修改上面的eSurveyQuestion 示例,以在问题文本上使用常量属性而不是变量属性,以指示一旦创建SurveyQuestion 实例,就无法修改问题。即使text属性现在是常量,仍然可以在初始化方法中设置:

类调查问题{

让text: 字符串

var response: 字符串?

初始化(text:字符串){

self.text=文本

}

函数询问(){

打印(文本)

}

}

let beetsQuestion=SurveyQuestion(text: "甜菜怎么样?")

甜菜问题.ask()

//print "甜菜怎么样?"

beetsQuestion.response="我也喜欢

e beets. (But not with cheese.)"

默认初始化方法

Swift 为任何结构体或者类提供一个默认初始化方法, 这些类和结构体给所有的属性提供了初始值,但是没有提供一个初始化方法。默认初始化方法简单创建一个新的实例,然后把所有属性设置为默认值。 这个例子定义了一个类 ShoppingListItem, 封装了购物列表中一项的名字, 数量和购买状态: class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem() 因为所有的 ShoppingListItem 属性都有默认值, 又因为它是基类没有超类, ShoppingListItem 会自动创建默认的初始化方法, 来创建实例并把所有属性设置成默认值。(name 属性是一个可选字符串类型属性, 它会自动设为 nil, 即使这个值没有写在代码里) 这个例子使用默认初始化方法来创建新实例, 初始化语法是 ShoppingListItem(), 然后赋值给一个变量 item.

结构体类型成员初始化方法

Structure 如果没有定义自己的初始化方法, 它会自动获取到一个成员初始化方法。跟默认初始化方法不同, 即使结构体有未设置默认值的存储属性,它也会接收到一个成员初始化方法。 成员初始化方法是简写方式,用来初始化新结构体实例的成员属性。新实例的初始值可以通过名字传给成员初始化方法。 下面的例子定义了一个结构体 Size, 它有两个属性 width 和 height. 两个属性根据初始值推断为 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. 注意,如果你为一个值类型定义一个初始化方法, 你将不再访问那种类型的默认初始化方法 (或者成员初始化方法, 如果它是一个结构体)。这种情况下复合初始化方法提供的额外必要的设置, 这个防止不小心被使用自动初始化方法规避。 备注:如果你想自定义值类型使用默认初始化方法和成员初始化方法来初始化, 同时使用你自定义的初始化方法, 那就在扩展里实现自定义初始化方法而不是作为值类型原先实现的部分。更多信息, 参见扩展。 下面的例子定义了一个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, 提供一个特定的原点和大小, 或者指定一个特定的中心点和大小。三个初始化选择用三个自定义的初始化方法表示: 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)  } } 第一个初始化方法, init(), 功能和结构体未定义初始化方法自动分配的默认初始化方法一样。这个初始化方法函数体为空, 有一堆空的大括号表示 {}. 调用这个初始化方法,返回一个 Rect 实例, 它的原点和大小都是默认值, 分别是 Point(x: 0.0, y: 0.0) 和 Size(width: 0.0, height: 0.0): let basicRect = Rect() // basicRect"s origin is (0.0, 0.0) and its size is (0.0, 0.0) 第二个初始化方法, init(origin:size:), 功能和结构体未定义初始化方法自动分配的成员初始化方法一样。这个初始化方法简单指定原点和大小参数值给对应的存储属性: let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) // originRect"s origin is (2.0, 2.0) and its size is (5.0, 5.0) 第三个初始化方法, init(center:size:), 稍微有点复杂。开始基于中心点和大小值计算一个合适的原点值。然后调用 (或者代理) init(origin:size:) 初始化方法, 存储新的原点值和大小到对应的属性: let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0)) // centerRect"s origin is (2.5, 2.5) and its size is (3.0, 3.0) init(center:size:) 初始化方法可以给对应的属性指定新的原点值和大小。不过, 对于 init(center:size:) 初始化方法来说采用已存在的构造器更加遍历。

类继承和初始化

类的所有存储属性—包含来自超类的任何属性—初始化的时候必须分配一个初始值。

125.指定构造器和便利构造器

指定初始化方法是类的主要初始化方法。一个指定初始化方法会完全初始化类引入的所有属性,同时会调用合适的超类初始化方法来继续超类链的初始化过程。 Classes 倾向于有很少的指定初始化方法, 对于类来说只有一个很平常。 指定构造器是通过初始化发生地的漏斗点, 通过它初始化继续往超类链上走。 每个类至少应该有一个指定构造器。在某些情况下, 这个需求通过继承超类的一个或者多个指定构造器来实现。 便利构造器是次要的, 对类来说只是支持初始化方法。你可以定义一个便利构造器来调用同类的指定构造器, 这个便利构造器设置指定构造器的参数为默认值。你可以定义一个便利构造器来创建类的实例,用作特定用途或者输入值类型。 如果你的类不需要便利构造器,你就不必要提供它们。什么时候要简化构造形式来节省时间或者让类的初始化意图更清楚,可以创建便利构造器。

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

类的指定构造器写法和值类型的简单构造器一样: 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 或者 nil). Swift 的初始化更加灵活, 可以让你自定义初始值, 可以应对0或者nil不是有效默认值的情况。 Swift 的编译器执行四项安全检查来确保两阶段初始化无错误的完成: 安全检查 1 一个指定构造器必须确保, 在向上代理超类构造器之前,所属类的所有属性都被初始化。 就像上面提及的, 对象的内存只考虑完全初始化一次所有的存储属性的初始状态。为了让这条规则满足, 一个指定构造器必须保证在向上传递前自己所有的属性都已经初始化。 安全检查 2 在给继承来的属性赋值前,一个指定构造器必须向上委托超类的构造器。如果不这样做, 指定构造器指定的新值会被超类自己的构造器给覆盖了。 安全检查 3 在给任意属性赋值前, 一个便利构造器必须委托另外一个构造器。(包括相同类定义的属性)。如果不这样做, 便利构造器指定的新值会被本类的指定构造器覆盖。 安全检查 4 一个构造器不能调用任何实例方法, 不能读任何实例属性的值, 或者直到第一阶段初始化完成才可以引用self. 第一阶段初始化结束后类的实例才完全有效。第一阶段类的实例有效后,属性只能被访问, 方法只能被调用。 这里是两阶段初始化的呈现, 基于以上四个安全检查: 阶段 1 一个指定构造器或者便利构造器在类里调用。 给类的实例分配内存。内存尚未初始化。 类的指定构造器确保所有存储属性都有一个值。这些存储属性的内存开始初始化。 指定构造器放手给超类的构造器,对它的存储属性执行同样的操作。 沿着继承链继续往上直到尽头。 一旦到了继承链的尽头, 继承链最后的类已经确保所有存储属性都有一个值。实例的内存被认为已经全部初始化, 第一阶段完成。. 阶段 2 从继承链尽头回来, 链中的每个指定构造器都有选择去继续定制实例。构造器现在可以访问 self 和改变属性, 也可以调用函数。 最后, 链中的任何便利构造器都有选择去定制实例并且使用 self. 这里展示第一阶段如何为一个假设的子类和超类寻找初始化调用的: 在这个例子中, 初始化开始于调用子类的便利构造器。这个便利构造器尚不能修改任何属性。它委托了本类的一个指定构造器。 指定构造器确保所有的子类属性都有一个值, 根据安全检查 1. 然后在它的超类调用一个指定构造器继续沿着链初始化。 超类指定构造器确保超类属性都有一个值。因为没有更深的超类要初始化, 所以也不需要更深的委托。 只要所有超类的属性都有一个初始值, 它的内存就被认为已经完成初始化, 那么阶段1就完成了。 这里是阶段2如何寻找相同的初始化调用:在这个例子中, 初始化开始于调用子类的便利构造器。这个便利构造器尚不能修改任何属性。它委托了本类的一个指定构造器。 指定构造器确保所有的子类属性都有一个值, 根据安全检查 1. 然后在它的超类调用一个指定构造器继续沿着链初始化。 超类指定构造器确保超类属性都有一个值。因为没有更深的超类要初始化, 所以也不需要更深的委托。 只要所有超类的属性都有一个初始值, 它的内存就被认为已经完成初始化, 那么阶段1就完成了。 这里是阶段2如何寻找相同的初始化调用: 子类的指定构造器现在有机会更进一步去定制实例 (尽管它不必要这么做)。 一旦子类指定构造器完成, 超类的指定构造器可以执行额外的定制(尽管它没有必要这么做)。 最后, 一旦子类的指定构造器完成, 原先调用的便利构造器就可以执行额外的定制。

初始化方法的继承和重写

不像 Objective-C 中的子类, Swift 子类默认不继承它的超类的初始化方法。 Swift 的方法防止了一种情况,在这种情况下来自一个超类的初始化方法被一个更专业的子类继承,并且用来这个子类的新实例,但是这个实例并未完全或者正确的初始化。 备注:超类的初始化方法在特定情况下被继承, 只有安全而且合适才会这么做。 如果你想让自定义子类展示和超类一样的一个或者多个初始化方法, 你可以在子类内提供这些初始化方法的自定义实现。 当你写一个子类初始化方法,这个方法匹配一个超类的指定构造器, 你可以提供这个指定构造器的重写。因此, 你必须在子类初始化方法定义前写上 override 修饰符。 即使你重写一个自动提供的默认初始化方法,也是这样。 跟一个重写的属性,方法或者下标一样, override 修饰符提醒Swift 去判断超类有这么一个指定构造器可以重写, 并且验证重写初始化方法的参数已经有目的的指定了。 备注:当重写超类指定构造器时你应该总是写上 override 修饰符, 即使子类初始化方法的实现是一个便利构造器。 相反, 如果你写一个子类初始化方法匹配超类的便利构造器, 超类便利构造器不能直接被子类调用。因此, 你的子类不能提供一个超类初始化方法的重写。结果是, 当提供一个超类便利构造器的匹配实现时,你不能写上 override 修饰符。 下面的例子定义了一个基类 Vehicle. 这个基类声明了一个存储属性 numberOfWheels, 带有一个默认值 0. numberOfWheels 被一个计算属性 description用来创建汽车特征的字符串描述: 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 修饰符标记。 init() 初始化方法先调用 super.init(), 这是超类的默认初始化方法。这个确保 numberOfWheels 属性在被子类修改前可以被 Vehicle 初始化。调用 super.init() 后, numberOfWheels 原有值会被新值2替代。 如果你创建一个 Bicycle 实例, 你可以调用它的继承来的计算属性 description 来查看 numberOfWheels 属性是否被更新了: let bicycle = Bicycle() print("Bicycle: (bicycle.description)") // Bicycle: 2 wheel(s) 备注:子类在初始化的时候可以改变继承来的变量属性, 但是无法改变继承来的常量属性。

自动初始化方法的继承

就像上面提到的, 默认情况子类并不继承超类的初始化方法。不过, 不过如果某种条件满足了,超类的初始化方法还是可以被自动继承的。在实践中, 这意味着你通常不需要重写初始化方法, 只要这样做安全,你可以用最小的代价继承超类的初始化方法。 假设你给子类的任何新属性都设定了默认值, 下面的规则适用: 规则 1 如果你的子类没有定义任何指定构造器, 它会自动继承超类所有的指定构造器。 规则 2 如果你的子类提供了超类所有指定构造器的实现—或者通过像规则1那样的继承, 或者提供自定义实现—然后它会自动继承超类所有的便利构造器。 即使你的子类添加更深的便利构造器,这个规则都适用。 备注:一个子类可以实现一个超类的指定构造器,来作为自己的便利构造器,这个是满足规则2的部分。

指定和便利构造器的初始化

下面的例子展示了指定构造器,便利构造器和自动构造器继承的活动。这个例子定义了三个类 Food, RecipeIngredient 和 ShoppingListItem 的继承关系, 并且展示了它们如何交互。 这个继承关系中的基类是 Food, 它是封装了一个食品名字的简单类。Food 类引入了一个单独的字符串属性 name 并且提供了两个构造器来创建 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"s name is "Bacon" init(name: String) 构造器是一个指定构造器, 因为它可以保证一个新的Food 实例的所有存储属性完全初始化。Food 类没有超类, 所以 init(name: String) 不需要调用 super.init() 来完成初始化。 Food 类同时提供了一个便利构造器, init(), 没有任何参数。 通过委托Food 类的init(name: String), init() 为新食物提供了一个默认的占位符名字 [Unnamed]: let mysteryMeat = Food() // mysteryMeat"s name is "[Unnamed]" 继承关系中的第二个类 RecipeIngredient 是Food 类的子类。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 属性, 这个是 RecipeIngredient 唯一的新属性。这个操作之后, 构造器向上委托基类的 init(name: String) 构造器。这个过程符合两阶段初始化的安全检查1. RecipeIngredient 同时定义了一个便利构造器, init(name: String), 它用名字来创建一个 RecipeIngredient 实例。任何 RecipeIngredient 实例没有显式数量的,这个便利构造器都会假设一个数量值为1. 这个便利构造器方便 RecipeIngredient 实例快速创建, 并且当创建一些单个数量的实例时可以避免代码重复。这个便利构造器简单调用了指定构造器, 传入数量值 1. init(name: String) 便利构造器跟指定构造器 init(name: String) 接受一样的参数。因为这个便利构造器重写了超类的指定构造器, 它必须标记 override 修饰符。 尽管 RecipeIngredient 提供了 init(name: String) 作为便利构造器, RecipeIngredient 也提供了所有超类的指定构造器的实现。因此, RecipeIngredient 也就自动继承了超类的所有便利构造器。 在这个例子里, RecipeIngredient 的超类是 Food, 它只有一个便利构造器 init(). 这个构造器会被 RecipeIngredient 继承。继承 init() 函数版本和 Food 版本是一样的。除了它是调用e RecipeIngredient 版本的 init(name: String) 而不是 Food 版本之外。 三个构造器都快要用来创建新的 RecipeIngredient 实例: let oneMysteryItem = RecipeIngredient() let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6) 继承关系里的最后一个类ShoppingListItem也是 RecipeIngredient 子类。ShoppingListItem 模拟了一个配方成分。 购物清单中的每一项都以“unpurchased”开始。为了说明这个事实, 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, 它包含了3个 ShoppingListItem 实例。 数组类型推断为 [ShoppingListItem]. 这个数组创建后, 数组开始项 ShoppingListItem 名字从”[Unnamed]” 变成 “Orange juice” 并且标记为已购买。打印数组中的每一项来表示它们的默认状态按照预期设置了。

可失败构造器

有时候定义可以构造失败的类, 结构体和枚举是很有用的。失败有很多触发的因素, 比如无效构造参数值, 必须的外部资源的缺失, 或者一些阻止构造成功的其他条件。 要处理这些引起失败的构造条件, 可以给类, 结构体或者枚举定义一个或者多个可失败的构造器。方法是在 init 关键字之后放置一个问号 (init?). 备注 :你不能同时使用相同的参数名和参数类型, 定义一个可失败的构造器和不会失败的构造器。 一个可失败的构造器会创建一个可选类型值。 在一个可失败的构造器中写 return nil 表明在这个点构造失败会被触发。 备注: 严格来说, 构造器不返回值。它们的角色是保证 self 在初始化结束时可以充分正确的构造。尽管你写 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 of (valueMaintained)") } // 打印 "12345.0 conversion to Int maintains value of 12345" 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, 有一个常量 String 属性 species. 这个结构体同时定义了一个可失败构造器, 它只有一个参数 species. 这个构造器判断 species 的值是否是一个空字符串。如果发现是一个空字符串, 就会触发一个构造失败。否则, 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" 如果你把空字符串传给可失败构造器的 species 参数, 就会触发构造失败: 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" 备注: 判断一个空字符串值不同于判断 nil. 在上面的例子里, 一个空字符串 (“”) 是一个有效的, 非可选的 String. 不过, 对于动物来说 species 属性值为空不合适。为了模拟这个限制, 如果发现空字符串, 这个可失败构造器就会触发一个构造失败。

枚举的可失败构造器

你可以基于一个或多个参数使用构造器去选择一个合适的枚举分支. 如果提供的参数没有匹配合适的分支,构造就会失败。 下面的例子定义了一个枚举 TemperatureUnit, 有三种可能的状态 (kelvin, celsius, 和 fahrenheit). 可失败构造器根据代表温度符号的字符值来寻找一个合适的枚举分支: 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 例子,然后使用 init?(rawValue:) 构造器: 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."

构造可失败的传递

在相同类,结构体或者枚举中可失败构造器可以委托调用另外的可失败构造器。相似的, 子类可失败构造器可以向上调用超类的可失败构造器。 另外一种情况, 如果你委托其他构造器导致构造失败, 那么整个构造过程就会立即失败, 后面的构造代码不会再执行。 备注:一个可失败构造器要可以委托调用一个非失败构造器。如果你需要给一个存在的初始化过程添加一个潜在的失败状态,可以用这个方法。 下面的例子定义了一个 Product 的子类 CartItem. CartItem 模拟了在线购物车的一项。CartItem 引入了一个存储常量属性 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和quantity大于等于1的值构造一个 CartItem 实例, 初始化就成功了: if let twoSocks = CartItem(name: "sock", quantity: 2) { print("Item: (twoSocks.name), quantity: (twoSocks.quantity)") } // 打印 "Item: sock, quantity: 2" 如果你用quantity 等于0来创建一个 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" 相似的, 如果你使用空名字去创建一个 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. 这个类模拟了一个文档,这个文档可以用一个非空字符串名字或者nil来初始化, 但是不能用一个空字符串: class Document { var name: String? // this initializer creates a document with a nil name value init() {} // this initializer creates a document with a nonempty name value init?(name: String) { if name.isEmpty { return nil } self.name = name    } } 下面的例子定义了 Document 的一个子类 AutomaticallyNamedDocument. AutomaticallyNamedDocument 子类重写了两个 Document 类引入的指定构造器。这些重写保证了一个 AutomaticallyNamedDocument 有一个初始值 “[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:). 因为 AutomaticallyNamedDocument 处理空字符串的情况和超类不同, 它的构造器不需要失败, 所以它提供了一个非失败的构造器版本。 你可以在构造器里使用强制拆包,来调用超类的可失败构造器。 例如, UntitledDocument 子类总是命名为 “[Untitled]”, 并且在它的初始化过程中,它会使用超类的可失败构造器init(name:) class UntitledDocument: Document { override init() { super.init(name: "[Untitled]")!     } } 在这种情况下, 如果超类的 init(name:) 构造器用空名字字符串调用, 强制拆包操作会引发一个运行时错误。不过, 它是用常量字符串调用的, 你可以看到这个构造器不会失败, 所以这种情况不会发生运行时错误。

init! 可失败构造器

通过在init关键之后面加上一个问号,你可以定义一个可失败构造器来创建一个对应类型的可选实例。或者, 你可以定义一个可失败构造器,来创建对应类型隐式拆包可选实例。 这个可以通过在init关键之后面加上感叹号而不是问好来实现。 你可以从init? 调用到 init! ,反之亦然, 而且你可以用init!重写 init? with init! ,反之亦然。你还可以从init 调用到 init!, 尽管这样做会触发一个断言,如果 init! 构造器导致构造失败的话。

必需构造器

在一个类的构造器前写上 required 修饰符来表明这个类的每个子类都要实现这些构造器: class SomeClass { required init() { // initializer implementation goes here     } } 你也必需在每一个实现必须构造器的子类构造器前写上 required 修饰符, 来表明在链中构造需求适用更远的子类。当重写一个必须指定构造器时,你不需要写上 override 修饰符: class SomeSubclass: SomeClass { required init() { // subclass implementation of the required initializer goes here    } } 备注:如果你可以用一个继承来的构造器满足要求,你就不必要提供一个显式的必须构造器。

用闭包或者函数设定默认属性值

如果一个存储属性的默认值需要定制或者设置, 你可以使用一个闭包或者一个全局函数。一旦一个属性所属的新的实例被初始化, 闭包或者函数就会调用, 然后返回值被赋值给属性的默认值。 这些闭包或者函数特别创建和属性相同类型的一个临时值, 返回的临时值用来作为这个属性的默认值。 这里有一个框架, 来展示一个闭包如何用来提供默认属性值: class SomeClass { let someProperty: SomeType = { // create a default value for someProperty inside this closure // someValue must be of the same type as SomeType return someValue     }() } 注意闭包花括号结束后跟着一对空括号。这个会通知 Swift 立即执行这个闭包。如果你忽略这对括号, 你在尝试把闭包本身赋值给这个属性, 而不是闭包的返回值。 备注:如果你用闭包初始化一个属性, 记住,闭包执行时,实例的其他部分还没有进行初始化。这就意味着你不能在闭包里访问其他任何属性, 即使这些属性具有默认值。你也不能使用隐式self 属性, 或是调用任何实例方法。 下面的例子定义了一个结构体 Chessboard, 它为象棋游戏模拟了一个棋盘。象棋是在一个 8 x 8 的去棋盘上玩的, 带着交替的黑白方格。 为了表示这个游戏棋盘, Chessboard 结构体有一个属性 boardColors, 它是一个带有64个布尔值的数组。真值代表黑色方块,假值代表白色方块。第一项代表棋盘的左上方块,最后一项代表棋盘的右下方块。 boardColors 数组使用一个闭包初始化,设定它的颜色值: struct Chessboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...8 {      for j in 1...8 {      temporaryBoard.append(isBlack)       isBlack = !isBlack          }       isBlack = !isBlack     } return temporaryBoard     }() func squareIsBlackAt(row: Int, column: Int) ->Bool { return boardColors[(row * 8) + column]    } } 一旦新的Chessboard 实例创建, 闭包就会执行, boardColors 默认值会计算和返回。上面例子里的闭包在一个临时数组temporaryBoard 为棋盘上每个方块计算和设置了对应的颜色值, 并且等设置完成就作为闭包返回值返回。返回数组值存储在 boardColors 中,并且可以用squareIsBlackAtRow 功能函数进行查询: let board = Chessboard() print(board.squareIsBlackAt(row: 0, column: 1)) // 打印 "true" print(board.squareIsBlackAt(row: 7, column: 7)) // 打印 "false"

用户评论

拥菢过后只剰凄凉

终于等到Swift 4.0了!期待学习新的功能

    有7位网友表示赞同!

涐们的幸福像流星丶

我一直在使用Swift,这个版本有什么值得关注的新亮点吗?

    有17位网友表示赞同!

风中摇曳着长发

希望Swift 4.0能优化性能,开发效率会更高。

    有10位网友表示赞同!

北染陌人

每次发布新版本都能学习到很多,这次也不例外!

    有10位网友表示赞同!

我一个人

在公司现在用的是Swift 3,什么时候才升级呢?

    有11位网友表示赞同!

汐颜兮梦ヘ

想看一看新的API和语法变化具体有哪些。

    有9位网友表示赞同!

命里缺他

学习编程语言最重要的是理解新的概念,Swift 4.0 会带来哪些新鲜感?

    有5位网友表示赞同!

念安я

之前看过Swift入门教程,这个版本应该还有更完善的资源吧?

    有9位网友表示赞同!

揉乱头发

不知道Swift 4.0对App开发有什么影响。

    有16位网友表示赞同!

素衣青丝

学习语言需要持续积累经验,这次肯定能拓展视野。

    有7位网友表示赞同!

ー半忧伤

看标题应该是继续讲解Swift语法和应用,期待深入了解。

    有15位网友表示赞同!

花菲

最近在做iOS开发,应该来研究下Swift 4.0 看看能不能提升效率。

    有19位网友表示赞同!

◆残留德花瓣

学习编程真的很有挑战性,希望这次的教程能讲得通俗易懂。

    有18位网友表示赞同!

又落空

每次新版本都让人兴奋,这是技术进步的缩影吧?

    有16位网友表示赞同!

╯念抹浅笑

关注这个系列文章很久了,终于等到Swift 4.0 的讲解!

    有13位网友表示赞同!

〆mè村姑

想看看新的语法特性能用来做什么样的应用场景。

    有13位网友表示赞同!

别伤我i

学习Swift 4.0 可以让我更好地掌握iOS开发。

    有10位网友表示赞同!

£烟消云散

之前一直想深入了解Swift,这个版本是个很好的机会。

    有19位网友表示赞同!

惦着脚尖摘太阳

程序员的世界总是充满变革,需要不断学习才能跟上步伐。

    有5位网友表示赞同!

【深入探讨 Swift 4.0:编程语言系列教程第五篇】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:520送女朋友红包金额指南:了解合适红包金额 下一篇:探索未知:揭开神秘世界的面纱