原文:Swift Tutorial: Initialization In Depth, Part 2/2
持续学习初始化,涵盖类的初始化、子类和快捷初始化办法,使你的 Swift 技能更上一层楼。
在本教程的榜首部分,你学习了 Swift 中 Struct
的初始化,并经过为 NASA 的火星载人使命创立一个发射序列数据模块来应用它。你完成了 Struct
的初始化,尝试了初始化托付,并了解了何时以及为何运用可失利和可抛出过错的初始化。
但还有更多要学的,NASA 依然需求你的帮助!在本教程的第二部分,你将学习 Swift 中类的初始化。类的初始化与 Objective-C 中的类初始化有很大不同,当榜首次在 Swift 中编写类的初始化器时,你会遇到许多不同的编译器过错。本教程将带你了解潜在的问题,并向你供给一切必要的解释,以防止这些编译器过错。
在本教程的这一部分,你会:
- 看到类的初始化托付等价物。
- 学习关于类的可失利和可抛出过错初始化器的额定规矩。
- 学习类是怎么在类的层次结构中托付初始化的,以及
- 学习类是怎么承继初始化器的
请保证先完成榜首部分,由于这儿你将在你所学的根底上进行学习。
开端
你能够持续在榜首部分的 Playground 上作业,或者创立一个新的 Playground。本部分的代码不依赖于榜首部分的任何代码,但本教程需求 Foundation 结构,所以要保证 Playground 导入Foundation。导入 UIKit 或 AppKit 也符合这一要求。
初始化托付
距离火星升空只要一年时间了,在发送发射体系代码之前,你现已完成了一切的使命,只要一项非常重要的使命:你需求完成代表不同火箭组件的模型。具体来说,便是存放火箭燃料和液态氧的罐子。 在这一过程中,你会看到初始化器托付与类的作业方式的不同。
指定初始化办法
将 RocketComponent
类增加到你的 Playground 的末尾:
// 火箭组件
class RocketComponent {
let model: String
let serialNumber: String
let reusable: Bool
// 指定初始化办法
// Init #1a - Designated
init(model: String, serialNumber: String, reusable: Bool) {
self.model = model
self.serialNumber = serialNumber
self.reusable = reusable
}
}
RocketComponent
是另一个简略类。它有三个常量存储特点和一个指定初始化办法。
还记住本教程榜首部分中的托付初始化办法吗?记住一个托付初始化办法链最终会以调用一个非托付初始化办法结束。在类的国际里,指定初始化办法仅仅非托付初始化办法的一个高档术语。就像对待 Struct
相同,这些初始化器担任为一切声明的没有默许值的非可选类型的存储特点供给初始值。
注释"// Init #1a - Designated"
不是有必要的,但随着你在本教程中的进一步深化,这些类型的注释将有助于保持你的初始化器的条理性。
在 Playground 的底部,增加如下代码,看看指定初始化办法在运转:
let payload = RocketComponent(model: "RT-1", serialNumber: "234", reusable: false)
快捷初始化办法
那么托付初始化办法呢?它们在类的国际里也有一个漂亮的名字 :]。在 RocketComponent
中完成以下快捷初始化办法:
// 快捷初始化办法
// Init #1b - Convenience
convenience init(model: String, serialNumber: String) {
self.init(model: model, serialNumber: serialNumber, reusable: false)
}
留意这看起来就像 Struct
中的托付初始化办法。唯一不同的是,你有必要在声明前加上关键字 convenience
。
在 Playground 的最终,运用快捷初始化办法来创立另一个 RocketComponent
的实例:
let fairing = RocketComponent(model: "Serpent", serialNumber: "0")
与 Struct
的作业方式相似,「快捷初始化办法」让你具有更简略的初始化办法,只需在内部调用它本身的「指定初始化办法」。
失利和抛出反常策略
规划可失利和可抛出反常的初始化器是一门艺术——幸亏它不需求绘画。一开端,你或许会发现自己写的代码难以阅览,所以下面是一些策略,能够最大限度地提高可失利和可抛出反常的初始化器的可读性。
从指定初始化办法中失利和抛出反常
比方说,这次使命中的一切火箭部件都以格式化的字符串报告它们的类型和序列号:类型后边有一个连字符,然后是序列号。例如,“Athena-003”。完成一个新的 RocketComponent
可失利指定初始化办法,它接纳这个格式化的标识符字符串:
// 可失利的指定初始化办法
// Init #1c - Designated
init?(identifier: String, reusable: Bool) {
let identifierComponents = identifier.components(separatedBy: "-")
guard identifierComponents.count == 2 else {
return nil
}
self.reusable = reusable
self.model = identifierComponents[0]
self.serialNumber = identifierComponents[1]
}
在持续之前,请实例化以下常量,看看这个初始化器的作业情况:
let component = RocketComponent(identifier: "R2-D21", reusable: true)
let nocomponent = RocketComponent(identifier: "", reusable: true)
请留意侧边栏中的 nonComponent
是怎么被正确设置为 nil
的,由于这个标识符并不遵从 model-serial 的格式。
从快捷初始化办法中失利和抛出反常
用下面的完成代替你刚才写的初始化器:
// 可失利的指定初始化办法
// Init #1c - Convenience
convenience init?(identifier: String, reusable: Bool) {
// 处理失利逻辑
let identifierComponents = identifier.components(separatedBy: "-")
guard identifierComponents.count == 2 else {
return nil
}
// 实践的初始化托付给指定初始化办法
self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
reusable: reusable)
}
这个版别愈加简明。
在编写初始化办法时,使「指定初始化办法」不失利,并让它们设置一切的特点。然后你的「快捷初始化办法」能够有失利逻辑,并将实践的初始化托付给「指定初始化办法」。
留意,这种办法有一个缺陷,与承继有关。别担心,我们将在本教程的最终一节评论怎么战胜这个缺陷。
子类
这便是关于根类初始化的一切常识。根类是不承继其他类的,便是你到目前为止一直在运用的。本教程的其余部分将要点介绍承继类的初始化。
公平的正告:这便是事情变得波动的当地! 类的初始化要比结构的初始化复杂得多,由于只要类支撑承继。
与Objective-C的不同
假如你曾经从未见过 Objective-C 代码,不要担心–你依然能够阅览本节内容。
假如你用过 Objective-C 编程,Swift 的类初始化会感觉很受约束。在类和承继方面,Swift 界说了许多新的、不那么明显的初始化规矩。假如你遇到履行这些初始化规矩的意外编译器过错,请不要感到惊奇。
不要在你的 Playground 上写任何这些 Objective-C 代码–你很快就会跳回你的 Swift Playground,但首要我将评论 Objective-C,以展现初始化的天真办法。
为什么 Swift 会有这么多规矩?考虑一下下面这个 Objective-C 的头文件:
@interface RocketComponent : NSObject
@property(nonatomic, copy) NSString *model;
@property(nonatomic, copy) NSString *serialNumber;
@property(nonatomic, assign) BOOL reusable;
- (instancetype)init;
@end
假定初始化器没有设置 RocketComponent
的任何特点。Objective-C 会主动将一切特点初始化为一个相似于空的值,比方 NO
或 0
或 nil
。底线:这段代码能够创立一个彻底初始化的实例。
请留意,带有非可选类型特点的等价类在 Swift 中不会被编译,由于编译器不知道用什么值来初始化特点。Swift 不会将特点主动初始化为空值;它只会将可选类型的特点主动初始化为 nil
。正如你在本教程的榜首部分所看到的,程序员有责任为一切非可选的存储特点界说初始值;不然 Swift 编译器会诉苦。
Objective-C 和 Swift 初始化行为之间的这种区别是了解一长串 Swift 类初始化规矩的根底。假定你更新了 Objective-C 的初始化办法,为每个特点设置初始值:
@interface RocketComponent : NSObject
@property(nonatomic, copy) NSString *model;
@property(nonatomic, copy) NSString *serialNumber;
@property(nonatomic, assign) BOOL reusable;
- (instancetype)initWithModel:(NSString *)model
serialNumber:(NSString *)serialNumber
reusable:(BOOL)reusable;
@end
RocketComponent
现在知道怎么在不运用空值的情况下初始化一个实例。这一次,Swift 的等价物会编译成功。
增加特点到子类
请看下面的 RocketComponent
在 Objective-C 子类和它的实例化:
@interface Tank : RocketComponent
@property(nonatomic, copy) NSString *encasingMaterial;
@end
Tank *fuelTank = [[Tank alloc] initWithModel:@"Athena" serialNumber:@"003" reusable:YES];
Tank
引进了一个新的特点,但没有界说一个新的初始化器。这不要紧,由于依据 Objective-C 的行为,新特点将被初始化为 nil
。留意 fuelTank
是一个由RocketComponent
(超类)完成的初始化器初始化的 Tank
。Tank
承继了RocketComponent
的initWithModel:serialNumber:reusable:
办法。
这便是事情在 Swift 中实在开端溃散的当地。要看到这一点,请在你的 Playground 上用 Swift 写出上面的 Tank 子类的对应代码。请留意,这段代码将不会被编译经过:
class Tank: RocketComponent {
let encasingMaterial: String
}
留意这个子类怎么引进了一个新的存储特点 encasingMaterial
。这段代码不能编译,由于 Swift 不知道怎么彻底初始化 Tank
的一个实例。Swift 需求知道应该用什么值来初始化新的encasingMaterial
特点。
你有三个挑选来处理这个编译器过错:
- 增加一个指定初始化办法,调用或重写超类
RocketComponent
的指定初始化办法。 - 增加一个快捷初始化办法,调用超类
RocketComponent
的指定初始化办法。 - 为存储的特点增加一个默许值。
让我们挑选方案3,由于它是最简略的。更新 Tank
子类,将 “Aluminum” 声明为encasingMaterial
的默许特点值。
class Tank: RocketComponent {
let encasingMaterial: String = "Aluminum"
}
它能够编译和运转! 不仅如此,你的努力还得到了报答:你能够运用从 RocketComponent
承继来的初始化器,而不需求给 Tank
自界说一个。
运用承继的初始化器
用这个代码实例化一个 Tank
:
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
这是处理短少初始化器的编译器过错的简略办法。这和 Objective-C 中的作业相同。但是,大多数情况下,你的子类不会主动承继其父类的初始化器。你将在后边看到这个动作。
了解在子类中增加存储特点的影响对于防止编译器过错至关重要。在为下一节做预备时,注释掉 fuelTank
和 liquidOxygenTank
的实例:
//let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
//let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
为子类增加指定初始化办法
Tank
的 encasingMaterial
是一个常量特点,默许值为 “Aluminum”。假如你需求实例化另一个不是用铝包裹的坦克,该怎么办?为了习惯这个要求,移除默许的特点值,使其成为一个变量而不是常量:
class Tank: RocketComponent {
var encasingMaterial: String
}
编译器的过错是:“Class ‘Tank’ has no initializers”。每个新引进的没有默许值的非可选存储特点的子类都需求至少一个指定初始化办法。这个初始化器应该接纳 encasingMaterial
的初始值,以及RocketComponent
父类中声明的一切特点的初始值。你现已为根类树立了指定初始化办法,现在是时分为子类树立一个了。
让我们在 “为子类增加特点”一节中树立方案1:增加一个指定初始化办法,调用或覆写父类 RocketComponent
的指定初始化办法。
你的榜首个冲动或许是像这样写指定初始化办法:
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.model = model
self.serialNumber = serialNumber
self.reusable = reusable
self.encasingMaterial = encasingMaterial
}
这看起来像你在本教程中树立的一切指定初始化办法。但是,这段代码不会被编译,由于 Tank
是一个子类。在 Swift 中,子类只能初始化由它本身引进的特点。子类不能直接初始化由父类引进的特点。正由于如此,「子类的指定初始化办法」有必要托付给「父类的指定初始化办法」来取得一切父类特点的初始化。
在 Tank
中增加以下指定初始化办法:
// 子类的指定初始化办法,内部调用父类的指定初始化办法
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
代码又开端成功编译了! 这个指定初始化办法有两个重要部分。
- 初始化该类自己的特点。在本例中,这仅仅
encasingMaterial
。 - 将其余的作业托付给父类的指定初始化办法,
init(model:serialNumber:reusable:)
。
类层级的两阶段初始化
回想一下,两阶段初始化是为了保证托付初始化办法在设置特点、托付和运用新实例方面以正确的次序进行作业。到目前为止,你现已看到了 Struct
的托付初始化办法和 Class
的快捷初始化办法的情况,它们在本质上是相同的。
还有一种托付初始化办法:子类的指定初始化办法。你在上一节中树立了一个这样的初始化器。这方面的规矩超级简略:在调用托付初始化办法之前,你只能设置子类引进的特点,并且在第二阶段之前,你不能运用新的实例。
为了看看编译器是怎么履行两阶段初始化的,将 Tank
的初始化器#2a更新如下:
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
self.encasingMaterial = encasingMaterial // ❌
}
编译失利是由于指定初始化办法没有初始化该子类在榜首阶段引进的一切存储特点。
像这样更新同一初始化器:
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
self.model = model + "-X" // ❌
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
编译器会诉苦说 model
不是变量。实践上不要这样做,但假如你要把这个特点从常量改为变量,那么你就会看到这个编译器过错:
这就出错了,由于子类的指定初始化办法不允许初始化任何不是由同一子类引进的特点。这段代码试图初始化 model
特点,它是由 RocketComponent
引进的,而不是 Tank
。
你现在应该能够很好地识别和修复这个编译器过错。
为了预备下一章节,将 Tank 的 2a 初始化器更新为本节之前一开端编写的样子:
// 子类的指定初始化办法,内部调用父类的指定初始化办法
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
撤销承继初始化办法
现在你现已回到了 Tank
的编译版别,撤销之前的 fuelTank
和 liquidOxygenTank
实例的注释:
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
fuelTank
和 liquidOxygenTank
不再实例化成功,所以这段代码导致了一个编译器过错。回顾一下,在给 Tank
增加指定初始化办法之前,这段代码编译和运转都很好。发生了什么事?
两个实例化办法都试图运用属于 RocketComponent
的初始化器来创立一个 Tank
实例,而 RocketComponent
是 Tank
的父类。问题是,RocketComponent
的初始化器只知道怎么初始化 RocketComponent
所引进的特点。这些初始化器对子类引进的特点没有预见性。因而,RocketComponent
的初始化器不能彻底初始化 Tank
,虽然 Tank
是一个子类。
这段代码在 Objective-C 中能够正常作业,由于 Tank
的 encasingMaterial
特点在调用任何初始化器之前会被运转时初始化为 nil
。所以在 Objective-C 中,Tank
承继 RocketComponent
的一切初始化器并无大碍,由于它们能够彻底初始化 Tank
。
虽然在 Objective-C 中允许主动承继初始化器,但这显然是不抱负的,由于 Tank
或许以为 encasingMaterial
总是被设置为一个实在的字符串值。虽然这或许不会导致溃散,但它或许会造成意想不到的副作用。Swift 不允许这种情况,由于这不安全,所以一旦你为子类创立了一个指定初始化办法,子类就会中止承继(父类)一切的初始化办法,包括指定和快捷初始化办法。
为了预备下一节,再次注释 fuelTank
和 liquidOxygenTank
的实例。
//let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
//let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
初始化办法的调用栈
简而言之:「快捷初始化办法」托付给「其他快捷初始化办法」,直到托付给「指定初始化办法」。
子类中的「指定初始化办法」有必要托付给来自直接承继的父类的「指定初始化办法」。
请记住,子类中的「指定初始化办法」不能托付给父类的「快捷初始化办法」:
别的,**子类中的「快捷初始化办法」不能托付给父类的「快捷初始化办法」。**快捷初始化办法只能在它们被界说的类中进行托付:
在一个类的层次结构中,每个类要么:
- 没有初始化办法;
- 一个或多个指定初始化办法;
- 一个或多个快捷初始化办法和一个或多个指定初始化办法;
请考虑以下类图:
假定你想用 D 的指定初始化办法来初始化 D 类的一个实例。初始化将调用 C 的指定初始化办法向上输送到 A 的指定初始化办法。
或者说你想用 E 的第二个快捷初始化办法来初始化 E 的一个实例。初始化将移到 E 的榜首个快捷初始化办法,然后移到 E 的指定初始化办法。在这一点上,初始化将调用 D 和 C 的指定初始化办法,最终到 A 的指定初始化办法。
假如有的话,类的初始化开端经过快捷初始化办法,然后进入指定初始化办法,最终经过每个父类的指定初始化办法。
向上托付的调用栈
将以下 LiquidTank
子类增加到你的 Playground 末尾:
class LiquidTank: Tank {
let liquidType: String
// Init #3a - Designated
init(model: String, serialNumber: String, reusable: Bool,
encasingMaterial: String, liquidType: String) {
self.liquidType = liquidType
super.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: encasingMaterial)
}
}
你将运用这个类的调用栈追寻初始化办法的履行。它有标准的子类初始化器:设置自己的特点,然后调用 super
上的初始化器。
给LiquidTank
增加以下两个快捷初始化办法:
// Init #3b - Convenience
convenience init(model: String, serialNumberInt: Int, reusable: Bool,
encasingMaterial: String, liquidType: String) {
let serialNumber = String(serialNumberInt)
self.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: encasingMaterial, liquidType: liquidType)
}
// Init #3c - Convenience
convenience init(model: String, serialNumberInt: Int, reusable: Int,
encasingMaterial: String, liquidType: String) {
let reusable = reusable > 0
self.init(model: model, serialNumberInt: serialNumberInt, reusable: reusable,
encasingMaterial: encasingMaterial, liquidType: liquidType)
}
现在运用LiquidTank
的快捷初始化办法#3c来实例化一个新的火箭推进剂燃料箱:
let rp1Tank = LiquidTank(model: "Hermes", serialNumberInt: 5, reusable: 1, encasingMaterial: "Aluminum", liquidType: "LOX")
这种初始化遵从以下流程,经过初始化器的漏斗:3c > 3b > 3a > 2a > 1a。它首要穿过 LiquidTank
的两个快捷初始化办法;然后持续托付给 LiquidTank
的指定初始化办法。从那里开端,初始化被托付给一切的父类、Tank
和RocketComponent
。这也是运用了你在本教程榜首部分学到的相同的规划决定:两个初始化器能够运用相同的参数称号集,运转时满足聪明,能够依据每个参数的类型找出要运用的参数。在这种情况下,初始化器被调用时用一个整数来表示可重复运用的次数,这意味着编译器会挑选初始化器#3c。
从头承继初始化办法(重写父类指定初始化办法)
请记住,一旦子类界说了它们自己的指定初始化办法,它们就不再承继初始化器。假如你想运用父类的初始化器来初始化一个子类,该怎么办?在子类中重写父类的指定初始化办法。
在 Tank
中增加以下指定初始化办法:
// 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// Init #2b - Designated
override init(model: String, serialNumber: String, reusable: Bool) {
self.encasingMaterial = "Aluminum"
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
这使得 RocketComponent
的一个初始化办法可用于实例化 Tank
实例。当你想运用一个父类的非承继的指定初始化办法来实例化一个子类时,你能够像上面的比如相同覆写它。
现在给 LiquidTank
增加以下指定的初始化办法:
// 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// Init #3d - Designated
override init(model: String, serialNumber: String, reusable: Bool) {
self.liquidType = "LOX"
super.init(model: model, serialNumber: serialNumber,
reusable: reusable, encasingMaterial: "Aluminum")
}
// 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// Init #3e - Designated
override init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.liquidType = "LOX"
super.init(model: model, serialNumber: serialNumber, reusable:
reusable, encasingMaterial: encasingMaterial)
}
这使得 RocketComponent
和 Tank
的指定初始化办法都能够用来实例化 LiquidTank
实例。 现在,这些指定初始化办法又开端作业了,撤销之前的fuelTank
和liquidOxygenTank
实例的注释:
// 假如子类自己完成了指定初始化办法,体系就不会默许承继父类的指定初始化办法和快捷初始化办法。
// 这时,假如子类还想持续运用父类的指定初始化办法,就有必要在子类中重写父类的指定初始化办法
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
// 当你把父类的「一切的指定初始化办法」都重写后,你也就主动承继了父类一切的快捷初始化办法了。
let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
榜首行调用重写的 RocketComponent
的指定初始化办法来实例化一个 Tank
。但是看看第二行:它是用 RocketComponent
的快捷初始化办法来实例化 Tank
的!你并没有覆写它。这便是你怎么让子类承继其父类的快捷初始化办法。
覆写一个父类的一切指定初始化办法,以便从头承继父类的快捷初始化办法。
为了更进一步,运用 RocketComponent
的快捷初始化办法在你的 Playground 底部实例化一个 LiquidTank
:
// LiquidTank 重写了父类一切的指定初始化办法,它也就主动取得了父类的一切快捷初始化办法。
let loxTank = LiquidTank(identifier: "LOX-1", reusable: true)
由于 LiquidTank
覆写了 Tank
的一切指定初始化办法。这意味着 LiquidTank
也就主动承继了 RocketComponents
的一切快捷初始化办法。
运用快捷初始化办法来重写
指定初始化办法应该对一切的存储特点进行非常基本的赋值,并且通常为每个特点设置一个初始值。任何时分你需求一个初始化办法,经过削减参数和/或对特点值做一些预处理来简化初始化,你应该把它变成一个快捷初始化办法。这便是为什么它们被称为快捷初始化办法,毕竟:它们使初始化比运用指定初始化办法更便利。
在上一节中,你用 Tank
中的指定初始化办法重写了 RocketComponent
的指定初始化办法。想想这个覆写在做什么:它覆盖了RocketComponent
的指定初始化办法,使初始化 Tank
实例愈加便利。初始化更便利,由于这个覆写不需求设置 encasingMaterial
的值。
当覆写一个父类的指定初始化办法时,你能够把它变成一个指定初始化办法或一个快捷初始化办法。要把 LiquidTank
中被覆写的初始化办法变成快捷初始化办法,把初始化器#3d和#3e的代码替换为:
// 重写父类的指定初始化办法,并将其设置为快捷初始化办法,内部调用本类的指定初始化办法
// Init #3d - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool) {
self.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: "Aluminum", liquidType: "LOX")
}
// 重写父类的指定初始化办法,并将其设置为快捷初始化办法,内部调用本类的指定初始化办法
// Init #3e - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.init(model: model, serialNumber: serialNumber,
reusable: reusable, encasingMaterial: encasingMaterial, liquidType: "LOX")
}
用子类的指定初始化办法来覆写父类指定初始化办法有一个害处。假如子类的指定初始化办法有逻辑,你就不能把一个快捷初始化办法托付给它。相反,你能够用快捷初始化办法覆写父类的指定初始化办法;这允许你托付给子类的指定初始化办法逻辑。接下来你将尝试这个:
承继层级的失利和抛出反常策略
给 LiquidTank
增加以下快捷初始化办法:
// 可失利的快捷初始化办法,调用本身的指定初始化办法
// Init #3f - Convenience
convenience init?(identifier: String, reusable: Bool, encasingMaterial: String,
liquidType: String) {
let identifierComponents = identifier.components(separatedBy: "-")
guard identifierComponents.count == 2 else {
return nil
}
self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType)
}
这个初始化办法看起来与 RocketComponent
的快捷初始化办法几乎相同。
运用这个新的快捷初始化办法来实例化一个新的 LiquidTank
:
let athenaFuelTank = LiquidTank(identifier: "Athena-9", reusable: true,
encasingMaterial: "Aluminum", liquidType: "RP-1")
很成功,但 LiquidTank
和 RocketComponent
的初始化办法中存在重复的代码。重复的代码会带来过错的或许性,所以这是不可取的。
为了遵从 DRY(Don’t Repeat Yourself)原则,给 RocketComponent
增加以下类办法:
static func decompose(identifier: String) -> (model: String, serialNumber: String)? {
let identifierComponents = identifier.components(separatedBy: "-")
guard identifierComponents.count == 2 else {
return nil
}
return (model: identifierComponents[0], serialNumber: identifierComponents[1])
}
这段代码重构了初始化办法中的重复代码,并将其放在两个初始化办法都可访问的当地。
留意:类办法被标记为
static
关键字。它们是在类型本身而不是在实例上调用的。你或许曾经没有这样想过,但是在类的完成中调用一个办法实践上是在调用一个实例办法。例如,self.doSomething()
。而这儿,你需求运用一个类办法,由于decompose(identifier:)
作为一个实例办法,要到第二阶段,即一切特点都被初始化之后才干运用。你在运用decompose(identifier:)
作为一个东西来计算初始化的特点值。我建议在你自己的类的初始化办法中玩玩调用办法,以掌握这个概念。
现在更新 RocketComponent
和 LiquidTank
的两个快捷初始化办法,以调用新的静态办法:
// 可失利的指定初始化办法
// Init #1c - Convenience
convenience init?(identifier: String, reusable: Bool) {
// 重复代码运用类办法优化
guard let (model, serialNumber) = RocketComponent.decompose(identifier: identifier) else {
return nil
}
self.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
和:
// 可失利的快捷初始化办法,调用本身的指定初始化办法
// Init #3f - Convenience
convenience init?(identifier: String, reusable: Bool, encasingMaterial: String,
liquidType: String) {
// 重复代码运用类办法优化
guard let (model, serialNumber) = RocketComponent.decompose(identifier: identifier) else {
return nil
}
self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType)
}
这是运用可失利快捷初始化办法的一个很好的示例,同时消除了任何冗余。你依然能够让初始化办法在需求时返回 nil
,并且你现已将一般代码重构到类办法中,以防止重复。
何去何从?
呼,要讲的东西太多了! 在这个两阶段的教程中,你现已学到了许多关于初始化的常识……并且多亏了你,榜首个前往火星的载人使命现已预备好起飞了。巨大的作业!
假如你想比较代码或仅仅想看看最终的代码,能够在这儿下载第二部分的完好 Playground。
要回顾你在这儿学到的东西,并阅览一切的初始化规矩和编译器安全检查,请阅览苹果的 Swift编程言语。
要想取得更多的练习,并在更广泛的结构和类的布景下看到初始化,请检查我们的书《Swift Apprentice》。我们也有一个很好的关于 Swift 中面向对象规划 的教程,其间涉及到初始化。
现在 Swift 是开源的,你能够在 Swift GitHub repo 的 docs 文件夹下找到许多与初始化有关的有趣文档。经过阅览这些文档,你能够了解到 Swift 团队对初始化功用和安全检查的理由。重视 Swift-evolution 和 Swift-evolution-announce 邮件列表,能够了解到行将到来的内容。
我们希望你喜欢这个教程,假如你有任何问题或定见,请参加下面的论坛评论!
附录
import Foundation
// 火箭组件
class RocketComponent {
let model: String
let serialNumber: String
let reusable: Bool
// 类办法
static func decompose(identifier: String) -> (model: String, serialNumber: String)? {
let identifierComponents = identifier.components(separatedBy: "-")
guard identifierComponents.count == 2 else {
return nil
}
return (model: identifierComponents[0], serialNumber: identifierComponents[1])
}
// 指定初始化办法
// Init #1a - Designated
init(model: String, serialNumber: String, reusable: Bool) {
self.model = model
self.serialNumber = serialNumber
self.reusable = reusable
}
// 快捷初始化办法
// Init #1b - Convenience
convenience init(model: String, serialNumber: String) {
self.init(model: model, serialNumber: serialNumber, reusable: false)
}
// 可失利的指定初始化办法
// Init #1c - Convenience
// convenience init?(identifier: String, reusable: Bool) {
// let identifierComponents = identifier.components(separatedBy: "-")
// guard identifierComponents.count == 2 else {
// return nil
// }
//
// self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
// reusable: reusable)
// }
// 可失利的指定初始化办法
// Init #1c - Convenience
convenience init?(identifier: String, reusable: Bool) {
// 重复代码运用类办法优化
guard let (model, serialNumber) = RocketComponent.decompose(identifier: identifier) else {
return nil
}
self.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
}
// 指定初始化器
let payload = RocketComponent(model: "RT-1", serialNumber: "234", reusable: false)
// 快捷初始化器
let fairing = RocketComponent(model: "Serpent", serialNumber: "0")
// 可失利初始化器
let component = RocketComponent(identifier: "R2-D21", reusable: true)
let nocomponent = RocketComponent(identifier: "", reusable: true)
class Tank: RocketComponent {
var encasingMaterial: String
// 子类的指定初始化办法,内部调用父类的指定初始化办法
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
// 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// Init #2b - Designated
override init(model: String, serialNumber: String, reusable: Bool) {
self.encasingMaterial = "Aluminum"
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
}
// 假如子类自己完成了指定初始化办法,体系就不会默许承继父类的指定初始化办法和快捷初始化办法。
// 这时,假如子类还想持续运用父类的指定初始化办法,就有必要在子类中重写父类的指定初始化办法
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true)
// 当你把父类一切的指定初始化办法都重写后,你也就主动承继了父类一切的快捷初始化办法了。
let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true)
class LiquidTank: Tank {
let liquidType: String
// Init #3a - Designated
init(model: String, serialNumber: String, reusable: Bool,
encasingMaterial: String, liquidType: String) {
self.liquidType = liquidType
super.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: encasingMaterial)
}
// Init #3b - Convenience
convenience init(model: String, serialNumberInt: Int, reusable: Bool,
encasingMaterial: String, liquidType: String) {
let serialNumber = String(serialNumberInt)
self.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: encasingMaterial, liquidType: liquidType)
}
// Init #3c - Convenience
convenience init(model: String, serialNumberInt: Int, reusable: Int,
encasingMaterial: String, liquidType: String) {
let reusable = reusable > 0
self.init(model: model, serialNumberInt: serialNumberInt, reusable: reusable,
encasingMaterial: encasingMaterial, liquidType: liquidType)
}
// // 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// // Init #3d - Designated
// override init(model: String, serialNumber: String, reusable: Bool) {
// self.liquidType = "LOX"
// super.init(model: model, serialNumber: serialNumber,
// reusable: reusable, encasingMaterial: "Aluminum")
// }
//
// // 重写父类的指定初始化办法,内部调用父类的指定初始化办法
// // Init #3e - Designated
// override init(model: String, serialNumber: String, reusable: Bool,
// encasingMaterial: String) {
// self.liquidType = "LOX"
// super.init(model: model, serialNumber: serialNumber, reusable:
// reusable, encasingMaterial: encasingMaterial)
// }
// 重写父类的指定初始化办法,并将其设置为快捷初始化办法,内部调用本类的指定初始化办法
// Init #3d - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool) {
self.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: "Aluminum", liquidType: "LOX")
}
// 重写父类的指定初始化办法,并将其设置为快捷初始化办法,内部调用本类的指定初始化办法
// Init #3e - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool,
encasingMaterial: String) {
self.init(model: model, serialNumber: serialNumber,
reusable: reusable, encasingMaterial: encasingMaterial, liquidType: "LOX")
}
// 可失利的快捷初始化办法,调用本身的指定初始化办法
// Init #3f - Convenience
// convenience init?(identifier: String, reusable: Bool, encasingMaterial: String,
// liquidType: String) {
// let identifierComponents = identifier.components(separatedBy: "-")
// guard identifierComponents.count == 2 else {
// return nil
// }
//
// self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
// reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType)
// }
// 可失利的快捷初始化办法,调用本身的指定初始化办法
// Init #3f - Convenience
convenience init?(identifier: String, reusable: Bool, encasingMaterial: String,
liquidType: String) {
// 重复代码运用类办法优化
guard let (model, serialNumber) = RocketComponent.decompose(identifier: identifier) else {
return nil
}
self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType)
}
}
let rp1Tank = LiquidTank(model: "Hermes", serialNumberInt: 5, reusable: 1, encasingMaterial: "Aluminum", liquidType: "LOX")
// LiquidTank 重写了父类一切的指定初始化办法,它也就主动取得了父类的一切快捷初始化办法。
let loxTank = LiquidTank(identifier: "LOX-1", reusable: true)
// 可失利的快捷初始化办法
let athenaFuelTank = LiquidTank(identifier: "Athena-9", reusable: true,
encasingMaterial: "Aluminum", liquidType: "RP-1")