swift 構造器

構造過程是使用類、結構體或枚舉類型的實例之前的準備過程。在新實例可用前必須執行這個過程,具體操作包括設置實例中每個存儲型屬性的初始值和執行其他必須的設置或初始化工作。

通過定義構造器來實現構造過程,這些構造器可以看做是用來創建特定類型新實例的特殊方法。與oc中的構造器不同,swift的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。

類的實例也可以通過定義析構器在實例釋放之前執行特定的清除工作。

存儲屬性的初始賦值

類和結構體在創建實例時,必須爲所有存儲屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態
可以在構造器中爲存儲型屬性賦初值,可以在定義屬性使爲其設置默認值。

構造器

構造器在創建某個特定類型的新實例是被調用。
    struct Fahrenheit {
        var temperature:Double
        init() {
            temperature = 32.0
        }
    }
    var f = Fahrenheit()
        print("\(f.temperature)")

默認屬性值

如前所述,可以在構造器中爲存儲型屬性設置初始值。同樣,可以在屬性聲明時爲其設置默認值。
   struct Fahrenheit {
        var temperature:Double = 32.0
    }

自定義構造過程

構造參數

自定義構造過程時,可以在定義中提供構造參數,指定所需值的類型和名字。構造參數的功能和語法跟函數和方法的參數相同。
下面例子中定義了一個包含攝氏度的結構體Celsius。它定義了兩個不同的構造器:
    struct Celsius {
        var temperatureInCelsius:Double
        init(fromFahrenheit fahrenheit:Double) {
            temperatureInCelsius = (fahrenheit - 32.0) / 1.8
        }
        init(fromKelvin kelvin:Double) {
            temperatureInCelsius = kelvin - 273.15
        }
    }
        let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
        let freezingPointOfWater = Celsius(fromKelvin:2713.15)

參數的內部名稱和外部名稱

跟函數和方法參數相同,構造參數也擁有一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。
然而,構造器並不像函數和方法那樣在括號前有一個可辨別的名字。因此在調用構造器時,主要通過構造器中的參數名和類型來確定應該被調用的構造器。正因爲參數如此重要,如果你在定義構造器時沒有提供參數的外部名字,swift會爲構造器的每個參數自動生成一個跟內部名字相同的外部名。
   struct Color {
        let red,green,blue:Double
        init(red:Double,green:Double,blue:Double) {
            self.red = red
            self.green = green
            self.blue = blue
        }
        
        init(white:Double) {
            red = white
            green = white
            blue = white
        }
    }
 let magenta = Color(red:1.0,green:0.0,blue:1.0)
        let halfGray = Color(white:0.5)
如果不通過外部參數名字傳值,沒有辦法調用這個構造器。只要構造器定義了某個外部參數名,必須使用它。

不帶外部名的構造器參數

如果不希望爲構造器的某個參數提供外部名字,可以使用下劃線來顯示描述它的外部名,以此重寫上面所說的默認行爲。

可選屬性類型

如果你定製的類型包含一個邏輯上允許取值爲空的存儲類型屬性——無論是因爲它無法再初始化時賦值,還是因爲它在之後某個時間點可以賦值爲空——都需要將它定義爲可選類型。可選類型的屬性將自動初始化爲nil,表示這個屬性是有意在初始化時設置爲空的。
    class SurveyQuestion {
        var text:String
        var response:String?
        init(text:String) {
            self.text = text
        }
        
        func ask() {
            print(text)
        }
    }
調查問題的答案在回答前是無法確定的,因此我們將屬性response聲明爲String?類型,或者說是可選字符串類型。當SurveyQuestion實例化時,將自動賦值爲nil,表明此字符串暫時還沒有值。

構造過程中常量屬性的修改

可以在構造過程中的任意時間點給常量屬性指定一個值,只要在構造過程結束時是一個確定的值。一旦常量屬性被賦值,它將永遠不可更改。

默認構造器

如果結構體或類的所有屬性都有默認值,同時沒有自定義的構造器,那麼swift會給這些結構體或類提供一個默認構造器。這個默認的構造器將簡單的創建一個所有屬性值都設置爲默認的實例。
    class ShoppingListItem {
        var name:String?
        var quantity = 1
        var purchased = false
    }
由於ShoppingListItem類中的所有屬性都有默認值,它是沒有父類的基類,將自動獲取一個可以爲所有屬性設置默認值的默認構造器。

類的繼承和構造過程

類裏面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構造過程中設置初始值。
swift爲類類型提供了兩種構造器來確保實例中所有存儲型屬性都能獲得初始值,他們分別是指定構造器和便利構造器。

指定構造器和便利構造器

指定構造器時類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。
每一個類都必須擁有至少一個指定的構造器。在某些情況下,許多類通過繼承了父類中的指定構造器而滿足了這個條件。
便利構造器時類中比較次要的、輔助型構造器。可以定義便利構造器來調用同一個類中的指定構造器,併爲其參數提供默認值。可以定義便利構造器來創建一個特殊用途或特定輸入值的實例。
應當只在必要的時候爲類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定的構造器,能夠節省更多時間並讓類的構造過程更清晰明瞭。

指定構造器和便利構造器的語法

指定構造器:
init(<#parameters#>) {
            <#statements#>
        }
便利構造器:
        convenience init(<#parameters#>) {
            <#statements#>
        }

類的構造器代理規則

爲了簡化指定構造器和便利構造器之間的調用關係,swift採用以下三條規則來限制構造器之間的代理調用:
1、指定的構造器必須調用其直接父類的指定構造器
2、便利構造器必須調用同類中定義的其它構造器
3、便利構造器必須最終導致一個指定構造器被調用。

構造器的繼承和重寫

跟oc中的子類不同,swift中的子類默認情況下不會繼承父類的構造器。swift的這樣的機制可以防止一個父類的簡單構造器被一個更精細 子類繼承,並被錯誤的用來創建子類的實例。
假如希望自定義的子類中能提供一個或多個跟父類相同的構造器,可以在子類中提供這些構造器的自定義實現。
當在編寫一個和父類中指定構造器相匹配的子類構造器時,實際上是在重寫父類的這個指定構造器。因此,必須在定義子類構造器時帶上override修飾符。即使你重寫的系統自動提供的默認構造器,也需要帶上override修飾符。
正如重寫屬性,方法或者下標,override修飾符會讓編譯器去檢查父類中是否有相匹配指定的構造器,並驗證構造器參數是否正確。
相反,如果你編寫了一個和父類便利構造器相匹配的子類構造器,由於子類不能直接調用父類的便利構造器,因此,嚴格意義上來講,子類並未對一個父類的構造器提供重寫,最後的結果是,子類中重寫一個父類便利構造器時,不需要override前綴。
    class Vehicle{
        var numberOfWheels = 0
        var description:String {
            return "\(numberOfWheels)"
        }
    }
    class Bicycle: Vehicle {
        override init(){
            super.init()
            numberOfWheels = 2
        }
    }
子類Bicycle定義了一個自定義指定構造器。這個指定的構造器和父類的指定構造器相匹配,所以帶有override。

構造器的自動繼承

子類在默認情況下不會繼承父類的構造器。但是如果滿足特定條件,父類的構造器可以被自動繼承的。在實踐中,這意味着對於許多常見的場景不必重寫父類的構造器,並且可以安全的情況下以最小的代價繼承父類的構造器。
建設你爲子類引入的所有新屬性都提供了默認值,以下2個規則適用:
1.如果子類沒有定義任何指定的構造器,它將自動繼承所有父類的指定構造器。
2.如果子類提供了所有父類指定構造器的實現——無論是通過1繼承過來的,還是提供自定義實現——它將自動繼承所有父類的便利構造器。

指定構造器和便利構造器實踐

接下來的例子將實踐中展示指定構造器、便利構造器以及構造器的自動繼承。
    
    class Food {
        var name:String
        init(name:String) {
            self.name = name
        }
        
        convenience init(){
            self.init(name:"[Unnamed]")
        }


    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)
        }
        
    }
    class ShoppingListItem: RecipeIngredient {
        var purchased = false
        var description:String {
            var output = "\(quantity) x \(name)"
            output += purchased ? " ?" : " ?"
            return output
        }
    }
由於它爲自己引入的所有屬性都提供了默認值,並自己沒有定義任何構造器,將自動繼承所有父類中的指定構造器和便利構造器。

可失敗構造器

如果一個雷、結構體或者枚舉類型的對象,在構造過程中可能失敗,則爲其定義一個可失敗構造器。這裏所指的“失敗”是指,給構造器傳入無效的參數,或缺少某種所需的外部資源,又或者不滿足某種必要的條件等。
爲了妥善處理這種構造過程中可能會失敗的情況。可以在一個雷,結構體或是枚舉類型的定義中,添加一個或多個可失敗構造器,其語法爲init關鍵字後面添加問號init?。
可失敗構造器會創建一個類型爲自身類型的可選類型的對象,通過return nil語句來表明可失敗構造器在何種情況下應該“失敗”。
    struct Animal {
        let species:String
        init?(species:String) {
            if species.isEmpty {
                return nil
            }
            self.species = species
        }
    }
可以通過該可失敗的構造器來構建一個Animal的實例,並檢查構造過程是否成功:
  let someCreature = Animal(species:"Giraffe")
        if let giraffe = someCreature {
            print("\(giraffe.species)")
        }
如果在這裏傳入的是空字符串就會導致構造失敗,返回的就是nil。

枚舉類型的可失敗構造器

可以通過一個帶一個或多個參數的可失敗構造器來獲取枚舉類型中特定的枚舉成員。如果提供的參數無法匹配任何枚舉成員,則構造失敗。
    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
            }
        }
    }
可以利用該可失敗構造器在三個枚舉成員中獲取一個相匹配的枚舉成員,當參數的值不能與任何成員相匹配,則構造失敗。

帶原始值的枚舉類型的可失敗構造器

帶原始值的枚舉類型會自帶一個可失敗構造器init?(rawValue:),該可失敗構造器有一個名爲rawValue的參數,其類型和枚舉類型的原始值類型一致,如果該參數的值能夠和某個枚舉成員的原始值匹配,則構造器會構造相應的枚舉成員,否則構造失敗。
上面的例子可以重寫爲:
    enum TemperatureUnit:Character {
        case Kelvin = "K",Celsius = "C",Fahrenheit = "F"
    }

構造失敗的傳遞

類,結構體,枚舉的可失敗構造器可以橫向代理到類型中的其他可失敗的構造器。類似的,子類的可失敗構造器也能向上代理到父類的可失敗構造器。
無論是向上代理還是橫向代理,如果代理奧的其他可失敗構造器觸發構造失敗,整個構造過程將立即終止,接下來的任何構造代碼不會再被執行。
    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值爲空字符串,則構造器立即執行失敗。

重寫一個可失敗構造器

如同其他的構造器,可以在子類中重寫父類的可失敗構造器。或者你可可以用子類的非可失敗構造器重寫一個父類的可失敗構造器,可以定義一個不會構造失敗的子類,即使父類的構造器允許構造失敗。
注意,當你用子類的非可失敗構造器重寫父類的可失敗構造器時,向上代理奧父類的可失敗構造器的唯一方式是對父類的可失敗構造器的返回值進行強制解包。
    class Document {
        var name:String?
        init() {
            
        }
        
        init?(name:String) {
            self.name = name
            if name.isEmpty {
                return nil
            }
        }
    }
下面的子類重寫了父類的兩個指定構造器,確保了無論是使用init()構造器,還是init(name:)構造器併爲參數傳遞空字符串,生成的實例中的name屬性總有初始值“[Untitled]”:
    class AutomaticallyNameDocument: Document {
        override init(){
            super.init()
            self.name = "[Untitled]"
        }
        
        override init(name:String){
            super.init()
            if name.isEmpty {
                self.name = "[Untitled]"
            }else{
                self.name = name
            }
        }
    }
子類用一個可失敗構造器重寫了父類的可失敗構造器。因爲子類用另一種方式處理了空字符串的情況,所以不再需要一個可失敗的構造器,因此子類用一個非可失敗的構造器代替了父類的可失敗的構造器。

可失敗構造器init!

通常來說我們通過在init關鍵字後添加問號的方式來定義一個可失敗構造器,但你也可以通過在init後面添加驚歎號的方式來定義一個可失敗的構造器,該可失敗構造器將會構建一個對應類型的隱式解包可選類型的對象。
可以在init?中代理到init!反之亦然,可以用init?重新init!,可以用init代理到init!,不過,一旦init!構造失敗,則會觸發一個斷言。

必要構造器

在類的構造器前添加required修飾符表明所有該類的子類都必須實現該構造器,在子類重寫父類的必要構造器時,必須在子類的構造器前也添加required修飾符,表明該構造器要求也應用於繼承鏈後面的子類。在重寫父類中必要的指定構造器時,不需要override修飾符。



選自《the swift programming language》中文版






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章