原文:Swift Tutorial: Initialization In Depth, Part 1/2
在这个关于深化探讨初始化的两部分教程中,经过了解你的实例是怎么被初始化的,将你的 Swift 技能提高到一个新的水平!
有些东西天然生成就很厉害:火箭、火星使命、Swift 的初始化。本教程将这三者结合在一起,让你了解到初始化的威力。 Swift 中的初始化是关于当你命名并创立一个新的实例类型时会产生什么:
let number = Float()
初始化是办理命名类型的存储特点的初始值的时候:类、结构和枚举。由于 Swift 内置的安全特性,初始化可能很扎手。有许多规矩,其间一些并不明显。
依照本教程的两部分内容,你将学会为你的 Swift 类型规划初始化器的内涵和外延。在榜首部分,你将从包括 Struct 初始化在内的基础常识开端,在第二部分,你将持续学习类的初始化。
在开端之前,你应该熟悉 Swift 中初始化的基础常识,并对一些概念感到满意,如可选类型,抛出反常和过错处理,以及声明默许的存储特点值。此外,请确保你安装了 Xcode 8.0 或更高版别。
假如你需求复习一下基础常识,或许你刚开端学习 Swift,能够看看咱们的书《Swift 学徒》或许咱们的许多 Swift 入门教程。
开端
让咱们设定一个场景:这是你在 NASA 担任发射软件工程师的榜首天(加油!)。你的使命是规划数据模型,该模型将驱动初次火星载人使命的发射序列,即 Mars Unum。当然,你做的榜首件事便是压服团队运用 Swift。然后……
打开 Xcode,创立一个名为 BlastOff 的新 Playground。你能够挑选任何渠道,由于本教程中的代码是不分渠道的,只依赖于 Foundation 框架。
在整个教程中,请记住这条黄金规矩:在一个实例完全初始化之前,你不能运用它。对一个实例的“运用”包括访问特点、设置特点和调用办法。除非另有阐明,本章节的一切内容都仅适用于 Struct
。
依据默许初始化器
为了开端对发射序列进行建模,在你的 Playground 上声明一个名为 RocketConfiguration
的新结构体:
struct RocketConfiguration {
}
在 RocketConfiguration
界说的结尾大括号下面,初始化一个名为 athena9Heavy
的常量实例。
let athena9Heavy = RocketConfiguration()
这儿运用了一个**默许初始化器(default initializer)**来实例化 athena9Heavy
。在默许初始化器中,类型的称号后边是空括号。当你的类型没有任何存储特点,或许类型的一切存储特点都有默许值时,你能够运用默许初始化器。这对 struct
和 class
都是如此。
在结构体界说中增加三个存储特点:
struct RocketConfiguration {
let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1
}
留意到默许的初始化器仍在作业。代码持续运转,由于一切存储的特点都有默许值。这意味着默许初始化器并没有太多的作业要做,由于你现已供给了默许值。
那么可选类型呢?在结构界说中增加一个名为 numberOfStageReuseLandingLegs
的可变存储特点:
struct RocketConfiguration {
let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1
var numberOfStageReuseLandingLegs: Int? = nil
}
在咱们的 NASA 方案中,一些火箭是可重复运用的,而另一些则不是。这便是为什么 numberOfStageReuseLandingLegs
是一个可选的 Int
类型。默许初始化器持续正常运转,由于可选的存储特点变量默许被初始化为 nil
。可是,关于常量来说,状况就不是这样了。
把 numberOfStageReuseLandingLegs
从一个 var
类型改为 let
类型:
struct RocketConfiguration {
let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1
// 假如结构体的存储特点是可选常量,且没有赋初始值,编译器报错
let numberOfStageReuseLandingLegs: Int?
}
留意 Playground 怎么陈述一条编译器过错:
你不会经常遇到这种状况,由于很少需求常量可选类型。为了处理编译器的过错,给 numberOfStageReuseLandingLegs
指定一个默许值为 nil
。
struct RocketConfiguration {
let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1
let numberOfStageReuseLandingLegs: Int? = nil
}
万岁! 编译器又高兴了,初始化也成功了。经过这样的设置,numberOfStageReuseLandingLegs
永远不会有一个非零的值。你不能在初始化后改动它,由于它被声明为一个常量。
依据成员初始化器
火箭通常是由多级组成的,这便是接下来要建模的内容。在 Playground 的底部声明一个名为 RocketStageConfiguration
的新结构:
struct RocketStageConfiguration {
let propellantMass: Double
let liquidOxygenMass: Double
let nominalBurnTime: Int
}
这一次,你有三个存储特点 propellantMass
、liquidOxygenMass
和nominalBurnTime
,没有默许值。 为火箭的榜首级创立一个RocketStageConfiguration
的实例:
let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1, liquidOxygenMass: 276.0, nominalBurnTime: 180)
RocketStageConfiguration
的存储特点都没有默许值。另外,RocketStageConfiguration
也没有完成初始化器。为什么没有呈现编译器过错?由于 **Swift Struct
(而且只有 Struct
)会主动生成一个成员初始化器(memberwise initializer)。**这意味着你能够为一切没有默许值的存储特点得到一个现成的初始化器。这真是太方便了,但也有几个问题。
幻想一下,当你提交这个代码片段进行审查时,你的开发团队领导告知你一切的特点应该按字母次第排列。 更新 RocketStageConfiguration
以从头排列存储的特点:
struct RocketStageConfiguration {
let liquidOxygenMass: Double
let nominalBurnTime: Int
let propellantMass: Double
}
产生了什么?stageOneConfiguaration
初始化器的调用不再有效,由于主动成员初始化器参数列表的次第反映了存储的特点列表的次第。要当心,由于当从头排列 Struct
中特点的声明次第时,你可能会损坏实例初始化。值得幸亏的是,编译器应该能捕捉到这个过错,但这绝对是一个需求留意的问题。
吊销对存储特点的从头排序操作,使 Playground 从头编译和运转:
struct RocketStageConfiguration {
let propellantMass: Double
let liquidOxygenMass: Double
let nominalBurnTime: Int
}
你一切的火箭都会燃烧 180 秒,所以每次实例化阶段装备时传递名义燃烧时刻是没有用的。将nominalBurnTime
的默许特点值设置为180
:
let nominalBurnTime: Int = 180
现在又呈现了一个编译器过错:
编译失利是由于成员初始化器只为没有默许值的存储特点供给参数。在这种状况下,成员式初始化器只接受推进剂质量和液氧质量,由于现已有了燃烧时刻的默许值。
删去nominalBurnTime
的默许值,这样就不会呈现编译器过错。
let nominalBurnTime: Int
接下来,给 Struct
增加一个自界说初始化器,为燃烧时刻供给一个默许值:
init(propellantMass: Double, liquidOxygenMass: Double) {
self.propellantMass = propellantMass
self.liquidOxygenMass = liquidOxygenMass
self.nominalBurnTime = 180
}
请留意,相同的编译器过错又呈现在 stageOneConfiguration
上。
等等,这不是应该作业吗?你所做的仅仅供给了一个代替的初始化器,可是原始的 stageOneConfiguration
初始化应该作业,由于它运用的是主动的成员初始化器。这便是问题所在:**只有当一个 Struct
没有界说任何初始化器时,你才干免费取得体系为你供给的成员初始化器。**一旦你界说了一个初始化器,你就失去了主动成员初始化器。
换句话说,Swift 会在开端时协助你。可是一旦你增加了你自己的初始化器,它就会以为你想让它脱离这儿。
从stageOneConfiguration
的初始化中移除nominalBurnTime
参数。
let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1, liquidOxygenMass: 276.0)
一切又都好了! :]
可是假如你仍然需求主动生成的成员初始化器呢?你当然能够写相应的初始化器,但那是一个很大的作业。相反,在实例化之前将自界说初始化器移到扩展中。
现在你的结构将分为两部分:主界说,以及带有两个参数初始化器的扩展:
struct RocketStageConfiguration {
let propellantMass: Double
let liquidOxygenMass: Double
let nominalBurnTime: Int
}
// 假如你自界说了一个初始化器,体系就不再供给默许的成员初始化器了
// 但假如你把自界说初始化器写在 extension 里边,鱼和熊掌兼得!
extension RocketStageConfiguration {
init(propellantMass: Double, liquidOxygenMass: Double) {
self.propellantMass = propellantMass
self.liquidOxygenMass = liquidOxygenMass
self.nominalBurnTime = 180
}
}
留意stageOneConfiguration
怎么持续用两个参数成功初始化。现在把nominalBurnTime
参数从头增加到stageOneConfiguration
的初始化中:
let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,
liquidOxygenMass: 276.0, nominalBurnTime: 180)
这也是可行的! 假如 Struct
主界说中不包括任何初始化器,Swift 仍会主动生成默许的成员初始化器。然后你能够经过扩展增加你的自界说初始化器,以取得一举两得的作用。
完成自界说初始化器
天气在发射火箭中起着关键作用,所以你需求在数据模型中处理这个问题。声明一个名为 Weather
的新结构,如下所示:
struct Weather {
let temperatureCelsius: Double
let windSpeedKilometersPerHour: Double
}
该结构现已存储了温度(摄氏度)和风速(公里/小时)的特点。
为 Weather
完成一个自界说的初始化器,输入温度(华氏度)和风速(英里/小时)。在存储特点下面增加这段代码:
init(temperatureFahrenheit: Double, windSpeedMilesPerHour: Double) {
self.temperatureCelsius = (temperatureFahrenheit - 32) / 1.8
self.windSpeedKilometersPerHour = windSpeedMilesPerHour * 1.609344
}
界说一个自界说的初始化器与界说一个办法十分类似,由于初始化器的参数列表与办法的参数列表的行为完全相同。例如,你能够为任何一个初始化器参数界说一个默许参数值。
将初始化器的界说改为:
init(temperatureFahrenheit: Double = 72, windSpeedMilesPerHour: Double = 5) {
self.temperatureCelsius = (temperatureFahrenheit - 32) / 1.8
self.windSpeedKilometersPerHour = windSpeedMilesPerHour * 1.609344
}
现在,假如你调用没有参数的初始化器,你会得到一些合理的默许值。在你的 Playground 文件的最终,创立一个 Weather
的实例并查看它的值:
let currentWeather = Weather()
currentWeather.temperatureCelsius
currentWeather.windSpeedKilometersPerHour
很酷,对吗?默许初始化器运用自界说初始化器供给的默许值。自界说初始化器的完成将这些值转换为公制等价物,并存储这些值。当你在 Playground 边栏查看存储特点值时,你会得到正确的摄氏度(22.2222)和每小时公里数(8.047)的值。
初始化器有必要为每一个没有默许值的存储特点赋值,否则你会得到一个编译器过错。记住,可选变量的默许值是 nil
。
接下来,改动 currentWeather
,运用你的自界说初始化器,并增加新的值:
let currentWeather = Weather(temperatureFahrenheit: 87, windSpeedMilesPerHour: 2)
currentWeather.temperatureCelsius
currentWeather.windSpeedKilometersPerHour
正如你所看到的,自界说值在初始化器中和默许值相同好用。Playground 侧边栏现在应该显现30.556度和3.219公里/小时。
这便是你怎么完成和调用自界说初始化器。你的天气结构现已准备好为你向火星发射人类的使命做出奉献。干得好!
运用初始化器托付防止重复作业
现在是时候考虑火箭的制导问题了。火箭需求花哨的制导体系来坚持它们完美的直线飞行。声明一个名为GuidanceSensorStatus
的新结构,代码如下:
struct GuidanceSensorStatus {
var currentZAngularVelocityRadiansPerMinute: Double
let initialZAngularVelocityRadiansPerMinute: Double
var needsCorrection: Bool
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Bool) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = needsCorrection
}
}
该结构持有火箭在Z轴上的当时和初始角速度(火箭的旋转程度)。该结构还记录了火箭是否需求修正以坚持在其方针轨迹上。
自界说初始化器持有重要的业务逻辑:怎么将每分钟的度数转换为每分钟的弧度。初始化器还设置了角速度的初始值,以备参考。
当辅导工程师呈现的时候,你正在愉快地编码。他们告知你,新版别的火箭会给你一个 Int
类型的 needsCorrection
,而不是一个 Bool
。工程师说,正整数应该被解释为真,而零和负应该被解释为假。你的团队还没有准备好改动代码的其他部分,由于这个改动是未来功能的一部分。那么,你怎样才干在坚持结构界说不变的状况下适应辅导工程师的要求呢?
不必忧虑——在榜首个初始化器下面增加以下自界说初始化器:
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = (needsCorrection > 0)
}
这个新的初始化器运用一个 Int
而不是 Bool
作为最终参数。可是,存储的needsCorrection
特点仍然是 Bool
,你能够依据他们的规矩正确设置。
不过在你写完这段代码后,心里的一些东西告知你,一定有更好的办法。初始化器其他部分的代码有太多的重复了!而且,假如初始化器中有过错的话,就会影响到咱们的作业。而且,假如在计算度数和弧度的转换中呈现了过错,你将不得不在多个地方修正它–这是一个能够防止的过错。这便是初始化器托付的用武之地。
把你方才写的初始化器替换成下面的内容:
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: (needsCorrection > 0))
}
这个初始化器是一个托付初始化器(delegating initializer),和它听起来相同,它将初始化托付给另一个初始化器。要进行托付,只需在self
上调用任何其他初始化器。
当你想供给一个备用的初始化器参数列表,但又不想重复自界说初始化器中的逻辑时,初始化托付就很有用。而且,运用托付初始化器有助于减少你所要写的代码量。
为了测验初始化器,实例化一个名为guideStatus
的变量:
let guidanceStatus = GuidanceSensorStatus(zAngularVelocityDegreesPerMinute: 2.2, needsCorrection: 0)
guidanceStatus.currentZAngularVelocityRadiansPerMinute // 0.038
guidanceStatus.needsCorrection // false
Playground 应该编译并运转,你为guidelinesStatus
特点查看的两个值将呈现在侧边栏中。
还有一件事——你被要求供给另一个初始化器,将 needsCorrection
默许为 false
。这应该很简单,只需创立一个新的托付初始化器,并在托付初始化之前设置里边的needsCorrection
特点即可。试着在该结构中增加以下初始化器,留意它不会被编译:
init(zAngularVelocityDegreesPerMinute: Double) {
self.needsCorrection = false
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: self.needsCorrection)
}
编译失利是由于托付初始化器实际上不能初始化任何特点。这是有原因的:你所托付的初始化器很可能掩盖你所设置的值,这是不安全的。托付的初始化器唯一能做的便是操作传递给另一个初始化器的值。 知道这一点后,删去新的初始化器,给主初始化器的needsCorrection
参数一个默许值false
。
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Bool = false) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = needsCorrection
}
经过移除needsCorrection
参数来更新governanceStatus
的初始化:
let guidanceStatus = GuidanceSensorStatus(zAngularVelocityDegreesPerMinute: 2.2)
guidanceStatus.currentZAngularVelocityRadiansPerMinute // 0.038
guidanceStatus.needsCorrection // false
干得好! 现在你能够把那些 DRY(Don’t Repeat Yourself)准则付诸实践了。
介绍两阶段初始化
到目前为止,你的初始化程序中的代码一直在设置你的特点和调用其他初始化程序。这是初始化的榜首阶段,但实际上,初始化一个 Swift 类型有两个阶段。
榜首阶段从初始化开端,在一切存储的特点都被赋值后完毕。剩下的初始化执行的是第二阶段。你不能在榜首阶段运用你正在初始化的实例,但你能够在第二阶段运用该实例。假如你有一个托付初始化器链,榜首阶段跨过调用栈,直到非托付初始化器。第二阶段跨过了从调用栈回来的过程。
将两阶段初始化付诸实施
现在你了解了两阶段初始化,让咱们把它应用到咱们的场景中。每个火箭发动机都有一个燃烧室,燃料与氧化剂注入其间,产生可控的爆炸,推动火箭。设置这些参数是榜首阶段的作业,为爆炸做准备。
完成下面的CombustionChamberStatus
结构,看看 Swift 的两阶段初始化是怎么进行的。请确保显现 Xcode 的 Debug 区域,以看到打印句子的输出:
struct CombustionChamberStatus {
var temperatureKelvin: Double
var pressureKiloPascals: Double
init(temperatureKelvin: Double, pressureKiloPascals: Double) {
print("Phase 1 init")
self.temperatureKelvin = temperatureKelvin
self.pressureKiloPascals = pressureKiloPascals
print("CombustionChamberStatus fully initialized")
print("Phase 2 init")
}
init(temperatureCelsius: Double, pressureAtmospheric: Double) {
print("Phase 1 delegating init")
let temperatureKelvin = temperatureCelsius + 273.15
let pressureKiloPascals = pressureAtmospheric * 101.325
self.init(temperatureKelvin: temperatureKelvin, pressureKiloPascals: pressureKiloPascals)
print("Phase 2 delegating init")
}
}
CombustionChamberStatus(temperatureCelsius: 32, pressureAtmospheric: 0.96)
你应该在调试区看到以下输出:
Phase 1 delegating init
Phase 1 init
CombustionChamberStatus fully initialized
Phase 2 init
Phase 2 delegating init
正如你所看到的,榜首阶段从调用托付初始化器 init(temperatureCelsius:pressureAtmospheric:)
开端,在此期间不能运用 self
。阶段1在 self.pressureKiloPascals
在非托付初始化器中被赋值后完毕。每个初始化器在每个阶段都扮演着一个角色。
编译器不是超级张狂的聪明吗?它知道怎么执行一切这些规矩。起初,这些规矩可能看起来很费事,但请记住,它们供给了许多的安全性。
假如事情出了过失怎么办?
你现已被告知,发射序列将是完全自主的,而且序列将执行许多的测验,以确保一切的体系都能在发射时正常运转。假如一个无效的值被传入初始化器,发射体系应该能够知道并作出反应。
在 Swift 中,有两种办法来处理初始化失利:运用可失利的初始化器,或许从初始化器中抛出过错。初始化失利的原因有许多,包括无效的输入、缺失的体系资源(如文件)以及可能的网络故障。
运用可失利的初始化器
普通初始化器和可失利初始化器之间有两个区别。**一个是「可失利初始化器」回来可选类型,另一个是「可失利初始化器」能够回来 nil
来表明初始化失利。**这可能十分有用——让咱们把它应用于咱们的数据模型中的火箭罐。
每个火箭级都有两个大罐子;一个装燃料,而另一个装氧化剂。为了跟踪每个油箱,完成一个名为TankStatus
的新结构,如下所示:
struct TankStatus {
var currentVolume: Double
var currentLiquidType: String?
init(currentVolume: Double, currentLiquidType: String?) {
self.currentVolume = currentVolume
self.currentLiquidType = currentLiquidType
}
}
let tankStatus = TankStatus(currentVolume: 0.0, currentLiquidType: nil)
这段代码没有什么问题,仅仅它不供认失利。假如你传入一个负的体积,会产生什么?假如你传入一个正的体积值但没有液体类型呢?这些都是失利的状况。你怎么运用可失利的初始化器来模拟这些状况?
首先,将TankStatus
的初始化器改为可失利初始化器,在init
上加一个 ?
:
init?(currentVolume: Double, currentLiquidType: String?) {
挑选点击tankStatus
,留意初始化器现在怎么回来一个可选的TankStatus
。
更新tankStatus
的实例化,使之与以下内容一致:
if let tankStatus = TankStatus(currentVolume: 0.0, currentLiquidType: nil) {
print("Nice, tank status created.") // Printed!
} else {
print("Oh no, an initialization failure occurred.")
}
实例化逻辑经过评价回来的optional
类型是否包括一个值来查看是否失利。
当然,还缺少一些东西:初始化器实际上还没有查看出无效的值。把可失利的初始化器更新为以下内容:
init?(currentVolume: Double, currentLiquidType: String?) {
if currentVolume < 0 {
return nil
}
if currentVolume > 0 && currentLiquidType == nil {
return nil
}
self.currentVolume = currentVolume
self.currentLiquidType = currentLiquidType
}
一旦检测到无效的输入,可失利初始化器就会回来nil
。在结构的可失利初始化器中,你能够在任何时候回来nil
。而类的可失利初始化器则否则,你将在本教程的第二部分看到。
要看到实例化失利,请在tankStatus
的实例化中传递一个无效的值:
if let tankStatus = TankStatus(currentVolume: -10.0, currentLiquidType: nil) {
留意 Playground 是怎么打印的:“Oh no, an initialization failure occurred.”。由于初始化失利了,失利的初始化器回来了一个nil
值,if let
句子执行了else
子句。
从初始化器中抛出过错
当回来nil
是一种挑选时,可失利的初始化器是很好的。关于更严重的过错,处理失利的另一种办法是从初始化器中抛出过错。
你还有最终一个结构需求完成:一个代表每个宇航员的结构。从写下面的代码开端:
// 宇航员
struct Astronaut {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
办理员告知你一个宇航员应该有一个非空的String
作为他或她的姓名特点,而且应该有一个从18到70的年龄。 为了表明可能的过错,在Astronaut
的完成之前增加以下过错枚举:
enum InvalidAstronautDataError: Error {
case EmptyName
case InvalidAge
}
这儿的枚举事例涵盖了你在初始化一个新的Astronaut
实例时可能遇到的问题。 接下来,用下面的完成替换Astronaut
的初始化器:
init(name: String, age: Int) **throws** {
if name.isEmpty {
throw InvalidAstronautDataError.EmptyName
}
if age < 18 || age > 70 {
throw InvalidAstronautDataError.InvalidAge
}
self.name = name
self.age = age
}
请留意,初始化器现在被标记为 throws
,让调用者知道会有过错。
假如检测到一个无效的输入值——要么是一个空字符串的姓名,要么是一个超出可接受规模的年龄–初始化器现在将抛出适当的过错。
经过实例化一个新的宇航员来试试这个:
let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 42)
这正是你处理任何旧的抛出办法或函数的办法。抛出初始化器的行为就像抛出办法和函数相同。你也能够传达抛出式初始化器的过错,并用 do-catch
句子来处理过错。这儿没有什么新东西。
要看到初始化器抛出一个过错,把johnny
的年龄改为17岁:
let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 17)
当你调用一个抛出的初始化器时,你写下 try
关键字–或许try?
或try!
的变体–来确定它能够抛出一个过错。在本例中,你运用了try?
所以在过错状况下回来的值是nil
。留意johnny
的值是nil
。遗憾的是,17岁关于太空飞行来说太年轻了。下一年会有更好的运气,Johnny!
可失利还是抛反常
运用抛出过错的初始化器并与 try?
句子结合运用看起来十分像运用可失利初始化器。那么你应该运用哪一种呢?
**考虑运用可抛出过错的初始化器。**可失利初始化器只能表达一种二进制的失利/成功状况。经过运用可抛出过错的初始化器,你不只能够表明失利,还能够经过抛出特定的 Error
类型来表明原因。另一个优点是,调用代码能够传达初始化器抛出的任何过错。
不过,可失利初始化器要简单得多,由于你不需求界说过错类型,而且你能够防止一切那些额外的 try?
关键字。
为什么 Swift 会有可失利初始化器?由于 Swift 的榜首个版别不包括 throw
函数,所以该言语需求一种办法来办理初始化失利的状况。
何去何从?
哇–你不只完成了将人类送上火星的一半使命,你现在还是一个 Swift 结构体的初始化大师了!你能够在这儿下载榜首部分的最终 Playground。
要了解一切关于Swift类初始化的常识,请持续阅读本教程的第二部分。
你能够在苹果《Swift编程言语攻略》的初始化章节中找到更多关于初始化的信息。假如你有任何问题或意见,请在下面的论坛中参加讨论!
附录
import Foundation
struct RocketConfiguration {
let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1
let numberOfStageReuseLandingLegs: Int? = nil
}
// 假如结构体的存储特点是可选常量,且没有赋初始值,编译器报错
// let numberOfStageReuseLandingLegs: Int?
let athena9Heavy = RocketConfiguration()
struct RocketStageConfiguration {
let propellantMass: Double
let liquidOxygenMass: Double
let nominalBurnTime: Int
}
// 假如你自界说了一个初始化器,体系就不再供给默许的成员初始化器了
// 但假如你把自界说初始化器写在 extension 里边,鱼和熊掌兼得
extension RocketStageConfiguration {
init(propellantMass: Double, liquidOxygenMass: Double) {
self.propellantMass = propellantMass
self.liquidOxygenMass = liquidOxygenMass
self.nominalBurnTime = 180
}
}
// 假如结构体中存储特点的声明次第产生改动,编译器报错
// 假如某个存储特点自带初始值,编译器报错
// let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1, liquidOxygenMass: 276.0)
let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,
liquidOxygenMass: 276.0, nominalBurnTime: 180)
struct Weather {
let temperatureCelsius: Double
let windSpeedKilometersPerHour: Double
init(temperatureFahrenheit: Double = 72, windSpeedMilesPerHour: Double = 5) {
self.temperatureCelsius = (temperatureFahrenheit - 32) / 1.8
self.windSpeedKilometersPerHour = windSpeedMilesPerHour * 1.609344
}
}
let currentWeather = Weather(temperatureFahrenheit: 87, windSpeedMilesPerHour: 2)
currentWeather.temperatureCelsius
currentWeather.windSpeedKilometersPerHour
struct GuidanceSensorStatus {
var currentZAngularVelocityRadiansPerMinute: Double
let initialZAngularVelocityRadiansPerMinute: Double
var needsCorrection: Bool
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Bool = false) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = needsCorrection
}
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: (needsCorrection > 0))
}
}
let guidanceStatus = GuidanceSensorStatus(zAngularVelocityDegreesPerMinute: 2.2)
guidanceStatus.currentZAngularVelocityRadiansPerMinute // 0.038
guidanceStatus.needsCorrection // false
struct CombustionChamberStatus {
var temperatureKelvin: Double
var pressureKiloPascals: Double
init(temperatureKelvin: Double, pressureKiloPascals: Double) {
print("Phase 1 init")
self.temperatureKelvin = temperatureKelvin
self.pressureKiloPascals = pressureKiloPascals
print("CombustionChamberStatus fully initialized")
print("Phase 2 init")
}
init(temperatureCelsius: Double, pressureAtmospheric: Double) {
print("Phase 1 delegating init")
let temperatureKelvin = temperatureCelsius + 273.15
let pressureKiloPascals = pressureAtmospheric * 101.325
self.init(temperatureKelvin: temperatureKelvin, pressureKiloPascals: pressureKiloPascals)
print("Phase 2 delegating init")
}
}
CombustionChamberStatus(temperatureCelsius: 32, pressureAtmospheric: 0.96)
// 油箱状态
struct TankStatus {
var currentVolume: Double
var currentLiquidType: String?
init?(currentVolume: Double, currentLiquidType: String?) {
if currentVolume < 0 {
return nil
}
if currentVolume > 0 && currentLiquidType == nil {
return nil
}
self.currentVolume = currentVolume
self.currentLiquidType = currentLiquidType
}
}
if let tankStatus = TankStatus(currentVolume: 0.0, currentLiquidType: nil) {
print("Nice, tank status created.") // Printed!
} else {
print("Oh no, an initialization failure occurred.")
}
enum InvalidAstronautDataError: Error {
case EmptyName
case InvalidAge
}
// 宇航员
struct Astronaut {
let name: String
let age: Int
init(name: String, age: Int) throws {
if name.isEmpty {
throw InvalidAstronautDataError.EmptyName
}
if age < 18 || age > 70 {
throw InvalidAstronautDataError.InvalidAge
}
self.name = name
self.age = age
}
}
// let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 42)
let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 17)