Swift 中的指定构造器和便利构造器 [译文]

此文章为本人翻译的译文,版权为原作者所有。
英文原文:Designated Initializers and Convenience Initializers in Swift

今天我们将了解有关类构造器的另一个部分,Swift 语言提供了两个不同类型的构造器,分别称为指定构造器便利构造器,他们其实已经存在于 Objective-C 中,但到了 Swift 中它们有了点小变化,并且也引入了一个非常有用的关键字。下面我们将讨论一下这个话题,看看它们如何结合起来使你的类更加好用。

指定构造器

像 Swift 中大部分的构造器,指定构造器的名字就非常恰当地解释了它所做的工作。它们是一个类最主要的构造器。一个类必须有一个指定构造器,但并不是说只能有一个。如果必要,你可以声明多个,但是通常情况一个就足够了。

下面我们来简单看看指定构造器是什么样子:

init(sender: String, recipient: String) {
    self.sender = sender
    self.recipient = recipient

    timeStamp = NSDate()
}

是不是看起来十分熟悉呢?我们之前有篇文章 Classes In Swift — An Introduction and Using a Nested Type in Swift (英文) 里介绍了一个 Message 类,这就是创建它的一个指定构造器的语法。我们的这个类只有一个构造器,所以它必须是指定构造器。创建它你只需要用到 init 这个关键字,配合几个需要用到的参数,就大功告成了。

便利构造器

便利构造器的作用也正如其名,他们就是用来让构造类的过程更简单。如果说指定构造器需要把所有的属性初始化好,并让用户自己传递这些属性的值,那便利构造器就是把其中几个属性硬编码了,这样我们就能用尽量少的参数去构造一个相同的对象了。通常,我们用便利构造器将对象用一些默认值进行初始化来让它们适合某种使用场景。

上面的构造器也许应该是一个便利构造器,因为它设置了 timeStamp 属性而没有让用户指定任何值。像我上面所说的,我认为指定构造器需要让用户传递所有需要设置的属性值(除非有些属性值是由其他参数值衍生计算而来的)。但这也只是个人偏好啦,上面那种写法作为指定构造器也是完全合法的,毕竟我们说 timeStamp 就是 Message 对象被构造的时间嘛。

尽管如此,让我们为我们的类添加一个便利构造器,让这个对象表示“它发送一个消息给它自己”。在这种情况下,我们就不需要传递两个不同的参数了,因为发送者就是接受者,所以我们声明一个只接受一个参数的便利构造器:

convenience init(sender: String) {
    self.init(sender: sender, recipient: sender)
}

它们在语法上是不同的,便利构造器有个 convenience 关键字在 init 之前,其他方面到时非常相似。因为我们只取了一个参数,所以我们就把这一个参数分别传递给指定构造器的两个参数。

两种构造器的使用原则

Swift 有三个有关两种构造器相互使用的原则,这里我直接引用 Apple 的 iBook 原文,就不进行解释了:

  1. 一个指定构造器必须调用它直系父类的一个指定构造器。
  2. 一个便利构造器必须调用这个类自身的另一个构造器。
  3. 一个便利构造器最终一定会调用一个指定构造器。



    引用自:Apple Inc. “The Swift Programming Language” iBooks

所以你能看到我们的便利构造器满足了上述的第 2 和 3 条原则。我们的指定构造器也处在类层次的最顶端,因此我们不需要调用父类的构造器(以满足第一天原则)。如果你重新调用了它,一定是它有一个子类,就像下面这个 TextMessage 类:

init(content: String, sender: String, recipient: String) {
    self.content = content
    super.init(sender: sender, recipient: recipient)
}

你看,它满足了第一条原则了。我们首先设置了这个类自身的 content 属性,然后调用了父类的指定构造器,这就完成这个类的初始化了。这也是和 Objective-C 的差异,我们在 Objective-C 中是先初始化父类,再设置自己的属性。当然,回到 Swift 中,如果需要你也可在调用完其父类后再设置继承来的属性。

根据 WWDC 的一个视频,这显然取决于类继承的工作方式。举个例子,如果一个父类调用了一个子类复写的方法,父类实际上将会调用子类复写后的方法实现(因为已经被复写了嘛),如果我们没有完全初始化好我们子类的属性,并且复写方法依赖于他们,那我们就遇到麻烦了。

这些原则有一些细微差别。在第二条原则中,一个便利构造器必须调用另一个构造器,其实不必是指定构造器,随便一个构造器都可以。如果你需要,你可以声明几个便利构造器,然后相互调用,这种链式调用时没问题的。但最终还是要符合第三条原则就对了。

举个例子吧:

convenience init() {
    self.init(content: "")
}

convenience init(content: String) {
    self.init(content: content, sender: "Myself")
}

convenience init(content: String, sender: String) {
    self.init(content: content, sender: sender, recipient: sender)
}

你能看到无参便利构造器传递了一个默认参数调用了另外一个便利构造器,另一个又是如此,但最终指定构造器还是被调用了。

我们之前声明的便利构造器设置了相同的 senderrecipient 来达到某些目的,为什么不子类化一个新类来解决这个问题呢?那么...

class NoteMessage: Message {
    let content: String

    init(content: String, theUser: String) {
        self.content = content
        super.init(sender: theUser)
    }

额,看来我们想当然了,别忘了第一条原则,一个指定构造器必须直接调用其直系父类的指定构造器。但我们尝试去调用它的一个便利构造器了,这看来是不行的。

我不知道为什么 Swift 设计成这样,毕竟一个便利构造器最终都会调用一个指定构造器。也许这迫使我们去思考最合适的默认属性值,而不是只顾便利。又或许以后这点会改变?谁知道呢。

总结 (译者按)

老外写文章有点啰嗦了,我这里也就不总结了,如果你认真阅读了本文,我相信你对指定构造器便利构造器一定有了很清晰的认识。总的来说就是,便利构造器是为你类的初始化工作提供方便的,它们最终一定要依赖于那些真正使你类能正常工作的初始化工作,这也就是指定构造器的工作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 本章将会介绍 存储属性的初始赋值自定义构造过程默认构造器值类型的构造器代理类的继承和构造过程可失败构造器必要构造器...
    寒桥阅读 749评论 0 0
  • 构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个...
    莽原奔马668阅读 661评论 0 3
  • 构造过程 构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置...
    蛊毒_阅读 708评论 0 2
  • 123.继承 一个类可以从另外一个类继承方法,属性和其他特征。当一个类继承另外一个类时, 继承类叫子类, 被继承的...
    无沣阅读 1,310评论 2 4
  • 午休过后是体育课,有些时候就是缘分来了挡也挡不住呀!高一B班——也就是齐月木他们班的体育课正好和程吏宇他们班的体育...
    见曦焉阅读 730评论 0 0