此文章為本人翻譯的譯文,版權為原作者所有遗遵。
英文原文: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
類鳄逾,這就是創(chuàng)建它的一個指定構造器的語法稻轨。我們的這個類只有一個構造器,所以它必須是指定構造器雕凹。創(chuàng)建它你只需要用到 init
這個關鍵字殴俱,配合幾個需要用到的參數,就大功告成了枚抵。
便利構造器
便利構造器的作用也正如其名线欲,他們就是用來讓構造類的過程更簡單。如果說指定構造器需要把所有的屬性初始化好汽摹,并讓用戶自己傳遞這些屬性的值李丰,那便利構造器就是把其中幾個屬性硬編碼了,這樣我們就能用盡量少的參數去構造一個相同的對象了逼泣。通常趴泌,我們用便利構造器將對象用一些默認值進行初始化來讓它們適合某種使用場景。
上面的構造器也許應該是一個便利構造器拉庶,因為它設置了 timeStamp
屬性而沒有讓用戶指定任何值嗜憔。像我上面所說的,我認為指定構造器需要讓用戶傳遞所有需要設置的屬性值(除非有些屬性值是由其他參數值衍生計算而來的)氏仗。但這也只是個人偏好啦吉捶,上面那種寫法作為指定構造器也是完全合法的,畢竟我們說 timeStamp
就是 Message
對象被構造的時間嘛皆尔。
盡管如此帚稠,讓我們?yōu)槲覀兊念愄砑右粋€便利構造器,讓這個對象表示“它發(fā)送一個消息給它自己”床佳。在這種情況下滋早,我們就不需要傳遞兩個不同的參數了,因為發(fā)送者就是接受者砌们,所以我們聲明一個只接受一個參數的便利構造器:
convenience init(sender: String) {
self.init(sender: sender, recipient: sender)
}
它們在語法上是不同的杆麸,便利構造器有個 convenience
關鍵字在 init
之前,其他方面到時非常相似浪感。因為我們只取了一個參數昔头,所以我們就把這一個參數分別傳遞給指定構造器的兩個參數。
兩種構造器的使用原則
Swift 有三個有關兩種構造器相互使用的原則影兽,這里我直接引用 Apple 的 iBook 原文揭斧,就不進行解釋了:
- 一個指定構造器必須調用它直系父類的一個指定構造器。
- 一個便利構造器必須調用這個類自身的另一個構造器。
- 一個便利構造器最終一定會調用一個指定構造器讹开。
引用自: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 的一個視頻,這顯然取決于類繼承的工作方式甥材。舉個例子盯另,如果一個父類調用了一個子類復寫的方法,父類實際上將會調用子類復寫后的方法實現(xiàn)(因為已經被復寫了嘛)洲赵,如果我們沒有完全初始化好我們子類的屬性鸳惯,并且復寫方法依賴于他們,那我們就遇到麻煩了叠萍。
這些原則有一些細微差別芝发。在第二條原則中,一個便利構造器必須調用另一個構造器苛谷,其實不必是指定構造器辅鲸,隨便一個構造器都可以。如果你需要腹殿,你可以聲明幾個便利構造器独悴,然后相互調用,這種鏈式調用時沒問題的锣尉。但最終還是要符合第三條原則就對了刻炒。
舉個例子吧:
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)
}
你能看到無參便利構造器傳遞了一個默認參數調用了另外一個便利構造器,另一個又是如此自沧,但最終指定構造器還是被調用了坟奥。
我們之前聲明的便利構造器設置了相同的 sender
和 recipient
來達到某些目的,為什么不子類化一個新類來解決這個問題呢?那么...
class NoteMessage: Message {
let content: String
init(content: String, theUser: String) {
self.content = content
super.init(sender: theUser)
}
額爱谁,看來我們想當然了晒喷,別忘了第一條原則,一個指定構造器必須直接調用其直系父類的指定構造器管行。但我們嘗試去調用它的一個便利構造器了厨埋,這看來是不行的邪媳。
我不知道為什么 Swift 設計成這樣捐顷,畢竟一個便利構造器最終都會調用一個指定構造器。也許這迫使我們去思考最合適的默認屬性值雨效,而不是只顧便利迅涮。又或許以后這點會改變?誰知道呢徽龟。
總結 (譯者按)
老外寫文章有點啰嗦了叮姑,我這里也就不總結了,如果你認真閱讀了本文据悔,我相信你對指定構造器和便利構造器一定有了很清晰的認識传透。總的來說就是极颓,便利構造器是為你類的初始化工作提供方便的朱盐,它們最終一定要依賴于那些真正使你類能正常工作的初始化工作,這也就是指定構造器的工作菠隆。