學習Swift筆記 (十六)Swift的初始化

構造過程是爲了適用某個類,結構體或枚舉類型的實例而進行的準備過程。這個過程包含了爲實例中的每個屬性設置初始化和爲其執行必要的準備和初始化任務。

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

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


存儲型屬性的初始賦值

類和結構體在實例創建時,必須爲所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。

你可以在構造器中爲存儲型屬性賦初值,也可以在定義屬性時爲其設置默認值。


注意:當你爲存儲型屬性設置默認值或者在構造器中爲其賦值時,他們的值是被直接設置的,不會觸發任何屬性觀測器(property observer)。


構造器

構造器在創建某特定類型的新實例時調用。它的最簡形式類似於一個不帶任何參數的實例方法,以關鍵字init命名。

下面例子定義一個用來保存華氏溫度的結構體Fahrenheit,它擁有一個Double類型的存儲型屬性temperature:

struct Fahrenheit {
    var temperature:Double
    init(){
        temperature = 32.0
    }
}

var f = Fahrenheit()
        println("The default temperature is \(f.temperature)° Fahenheit")
        //The default temperature is 32.0° Fahenheit

這個結構體定義了一個不帶參數的構造器init,並在裏面將存儲型屬性temperature的值初始化爲32.0(華攝氏度下水的冰點)。


默認屬性值

如前所述,你可以在構造器中爲存儲型屬性設置初始值;同樣,你也可以在屬性聲明時爲其設置默認值。

注意:如果一個屬性總是使用同一個初始值,可以爲其設置一個默認值。無論定義默認值還是在構造器中賦值,最終它們實現的效果是一樣的,只不過默認值跟屬性構造過程結合的更緊密。使用默認值能讓你的構造器更簡潔,更清晰,且能通過默認值自動推導出屬性的類型;同時,它也能讓你充分利用默認構造器,構造器繼承等特性。


你可以使用更簡單的方式在定義結構體Fahrenheit時爲屬性temperature設置默認值:

struct Fahrenheit {
    var temperature = 32.0
}


定製化構造過程

你可以通過輸入參數和可選屬性類型來定製構造過程,也可以在構造過程中修改常量屬性。


構造參數

你可以在定義構造器時構造參數,爲其提供定製化構造所需值的類型和名字。構造器參數的功能和語法跟函數和方法參數相同。


下面例子中定義了一個包含攝氏度溫度的結構體Celsius。它定義了兩個不同的構造器:init(fromFahrenheit:)和init(fromKelvin:),二者分別通過接受不同刻度表示的溫度值來創建新的實例:

struct Celsius {
    var temperatureInCelsius:Double = 0.0
    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: 273.15)
        println("BOIL IS \(boilingPointOfWater.temperatureInCelsius) FREE is \(freezingPointOfWater.temperatureInCelsius)")
        //BOIL IS 100.0 FREE is 0.0


第一個構造器擁有一個構造參數,其外部名字爲fromFahrenheit,內部名字爲fahrenheit;第二個構造器也擁有一個構造參數,其外部名字爲fromKelvin,內部名字爲kelvin。這兩個構造器都將唯一的參數值轉換成攝氏溫度值,並保存在屬性temperatureInCelsius中。


內部和外部參數名

跟函數和方法參數相同,構造參數也存在一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。

然而,構造器並不像函數和方法那樣在括號前有一個可辨別的名字。所以在調用構造器時,主要通過構造器中的參數名和類型來確定需要調用的構造器。正因爲參數如此重要,如果你在定義構造器時沒有提供參數的外部名字,Swift會爲每個構造器的參數自動生成一個跟內部名字相同的外部名,就相當於在每個構造參數之前加了一個哈希符號。


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


以下例子中定義了一個結構體Color,它包含了三個常量:red,green和blue。這些屬性可以存儲0.0到1.0之間的值,用來指示顏色中紅,綠,藍成分的含量。

Color提供了一個構造器,其中包含三個Double類型的構造參數:


struct Color {
    let red = 0.0,green = 0.0,blue = 0.0
    init(red:Double,green:Double,blue:Double){
        self.red = red
        self.green = green
        self.blue = blue
    }
}

每當你創建一個新的Color實例,你都需要通過三種顏色的外部參數名來傳值,並調用構造器。

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

注意:如果不通過外部參數名字傳值,你是沒法調用這個構造器的。只要構造器定義了某個外部參數名,你就必須使用它,忽略它將導致編譯錯誤:

let veryGreen = Color(0.0,1.0,0.0)


可選屬性類型

如果你定製的類型包含一個邏輯上允許取值爲空的存儲型屬性——不管是因爲它無法在初始化時賦值,還是因爲它可以在之後某個時間點可以賦值爲空——你都需要將它定義爲可選類型optional type。可選類型的屬性將自動初始化爲空nil,表示這個屬性是故意在初始化時設置爲空的。

下面例子中定義了類SurveyQuestion,它包含一個可選字符串屬性response:

struct SurveyQuestion {
    var text:String
    var response:String?
    init(text:String){
        self.text = text
    }
    func ask(){
        println(text)
    }
}

let cheeseQuestion = SurveyQuestion(text: "DO YOU LIKE CHHEESE")
        cheeseQuestion.ask()
        //DO YOU LIKE CHHEESE

調查問題在問題提出之後,我們才能得到回答。所以我們將屬性回答response聲明爲String?類型,或者說是可選字符串類型optional String。當SurveyQuestion實例化時,它將自動賦值爲空nil,表名暫時還不存在此字符串。


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

只要在構造過程結束前常量的值能確定,你可以在構造過程中的任意時間點修改常量屬性的值。

注意:對某個類實例來說,它的常量屬性只能在定義它的類的構造過程中修改;不能在子類中修改。

你可以修改上面的SurveyQuestion示例,用常量屬性替代變量屬性text,指明問題內容text在其創建之後不會在被修改。儘管text屬性現在是常量,我們仍然可以在其類的構造器中修改它的值:

class<pre name="code" class="html">struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

SurveyQuestion { let text:String var response:String? init(text:String){ self.text = text } func ask(){ println(text) }}


let beetsQuestion = SurveyQuestion(text: "HOW ABOUT BEETS")
        beetsQuestion.ask()
        //HOW ABOUT BEETS


默認構造器

Swift將爲所有屬性已提供默認值的且自身沒有定義任何構造器的結構體或基類,提供一個默認的構造器。這個默認構造器將簡單的創建一個所有屬性值都設置爲默認值的實例。

下面例子中創建一個類ShoppingListItem,它封裝了購物清單中的某一項的屬性:名字(name),數量(quantity)和購買狀態(purchase state)。

class ShoppingListItem{
    var name:String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

由於ShoppingListItem類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以爲所有屬性設置默認值的默認構造器(儘管代碼中沒有顯式爲name屬性設置默認值,但由於name是可選字符串類型,它將默認設置爲nil)。上面例子中使用默認構造器創造了一個ShoppingListItem類的實例(使用ShoppingListItem()形式的構造器語法),並將其賦值給變量item。


結構體的逐一成員構造器

除上面提到的默認構造器,如果結構體對所有存儲型屬性提供了默認值且自身沒有提供定製的構造器,他們能自動獲得一個逐一成員構造器。

逐一成員構造器是用來初始化結構體新實例裏成員屬性的快捷方法。我們在調用逐一成員構造器時,通過與成員屬性名相同的參數名進行傳值來完成對成員屬性的初始賦值。

下面例子中定義了一個結構體,它包含兩個屬性width和height。Swift可以根據這兩個屬性的初始賦值0.0子自動推導出他們的類型Double。

由於這兩個存儲型屬性都有默認值,結構體Size自動獲得了一個逐一成員構造器init(width:height:)。你可以用它來爲Size創建新的實例:

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

值類型的構造器代理

構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程爲構造器代理,它能減少多個構造器間的代碼重複。

構造器代理的實現規則和形式在值類型和類類型中有所不同。值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因爲它們只能代理任務給本身提供的其他構造器。類則不同,它可以繼承自其它類,這意味着類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化。

對於值類型,你可以使用self.init在自定義的構造器中引用其他的屬於相同值類型的構造器。並且你只能在構造器內部調用self.init。

注意:如果你爲某個值類型定義了一個定製的構造器,你將無法訪問到默認構造器(如果是結構體,則無法訪問逐一對象構造器)。這個限制可以防止你在爲值類型定義了一個更復雜的,完成了重要準備構造器之後,別人還是錯誤的使用了那個自動生成的構造器。

注意:加入你想通過默認構造器、逐一對象構造器以及你自己定製的構造器爲值類型創建實例,我們建議你將自己定製的構造器寫到擴展(extension)中,而不是跟值類型定義混在一起。


下面的例子將定義一個結構體Rect,用來展現幾何矩形。這個例子需要兩個輔助的結構體Size和Point,它們各自爲其所有的屬性提供了初始值0.0.

struct Size {
    var width = 0.0,height = 0.0
}

struct Point{
    var x = 0.0, y = 0.0
}

你可以通過以下三種方式爲Rect創建實例——使用默認的0值來初始化origin和size屬性;使用特定的origin和size實例來初始化;使用頁頂的center和size來初始化。

在下面Rect結構體定義中,我們爲這三種方式提供了三個自定義的構造器:

struct Rect {
    var origin = Point()
    var size = Size()
    init(){}
    init(origin:Point, size:Size){
        self.origin = origin;
        self.size = size;
    }
    init(center:Point, size: Size){
        let originX = center.x - (size.width/2)
        let originY = center.y - (size.height/2)
        self.init(origin:Point(x:originX,y:originY),size:size)
    }
}

第一個Rect構造器init(),在功能上跟沒有自定義構造器時自動獲得的默認構造器是一樣的。這個構造器是一個空函數,使用一對大括號{}來描述,他沒有執行任何定製的構造過程。調用這個構造器將返回一個Rect實例,它的origin和size屬性都使用定義時的默認值Point(x:0.0,y:0.0)和Size(width:0.0,height:0.0):

let basicRect = Rect()
//basic 的原點是(0.0,0.0),尺寸是(0.0,0.0)

第二個Rect構造器init(origin:size:),在功能上跟結構體在沒有自定義構造器時獲得的逐一構造器是一樣的。這個構造器只是簡單的將origin和size的參數值賦給對應的存儲型屬性:

let originRect = Rect(origin: Point(x:2.0,y:2.0), size: Size(width: 5.0, height: 5.0))
//originRect的原點(2.0,2.0),尺寸是(5.0,5.0)

第三個Rect構造器init(center:size:)稍微複雜一點。它先通過center和size的值計算出origin的座標。然後再調用(或代理給)init(origin:size:)構造器來將新的origin和size值賦值到對應的屬性中:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
//centerRect的原點(2.5,2.5) 尺寸是(3.0,3.0)

構造器init(center:size:)可以自己將origin和size的新值賦值到對應的屬性中。然而儘量利用現有的構造器和它所提供的功能來實現init(center:size:)的功能,是更方便,更清晰和更直觀的方法。

注意:如果你想用另外一種不需要自己定義init()和init(origin:size:)的方式來實現這個例子,請參考擴展。


類的繼承和構造過程

類裏面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構造過程中設置初始值。

Swift提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是制定構造器和便利構造器。


制定構造器和便利構造器

制定構造器是類中最主要的構造器。一個制定構造器將初始化類中提供的所有屬性,並根據父類往上調用父類的構造器來實現父類的初始化。

每一個類都必須擁有至少一個制定構造器。在某些情況下,許多類通過繼承了父類的制定構造器而滿足了這個條件。(參考自動構造器的繼承)。

便利構造器是類中比較次要的,輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,併爲其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。

你應當只在必要的時候爲類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清晰。


構造器鏈

爲了簡化指定構造器和便利構造器之間的調用關係,Swift採用以下三條規則來限制構造器之間的代理調用:

規則1:

指定構造器必須調用其直接父類的指定構造器。

規則2:

便利構造器必須調用同一類中定義的其他構造器。

規則3:

便利構造器必須最終以調用一個指定構造器結束。

更方便記憶的方法是:

指定構造器必須總是向上代理,便利構造器總是橫向代理。


兩段式構造過程

Swift中類的構造過程包含兩個階段。第一個階段,每個存儲型屬性通過引入他們的類的構造器來設置初始值。當每一個存儲型屬性值被確定後,第二階段開始,他給每個類一次機會在新實例準備使用之前進一步定製它們的存儲型屬性。

兩段式構造過程的使用讓構造過程更安全,同時在整個類層級結構中給予了每個類完全的靈活性。兩段式構造過程可以防止屬性值在初始化之前被訪問;也可以防止屬性被另外一個構造器意外的賦予不同的值。

注意:

Swift的兩段式構造過程跟OC中的構造過程類似。最主要的區別在於階段1,OC給每一個屬性賦值0或空值(比如0或者nil)。Swift的構造流程則更加靈活,它允許你設置定製的初始值,並自如應對某些屬性不能以0或nil作爲合法默認值的情況。


Swift編譯器將執行4種有效的安全檢查,以確保兩段式構造過程能順利完成:

安全檢查1:

指定構造器必須保證它所在類引入的所有屬性都必須先初始化完成,之後才能將其它構造任務向上代理給父類中的構造器。

一個對象的內存只有在其所有存儲型屬性確定之後才能完全初始化。爲了滿足這一規則,指定構造器必須保證它所在類引入的屬性在它往上代理之前先完成初始化。

安全檢查2:

指定構造器必須先向上代理調用父類構造器,然後再爲繼承的屬性設置新值。如果沒這麼做,指定構造器賦予的新值將被父類中的構造器所覆蓋。

安全檢查3:

便利構造器必須先代理調用同一類中的其它構造器,然後再爲任意屬性賦新值。如果沒有這麼做,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋。

安全檢查4:

構造器在第一階段構造完成之前,不能調用任何實例方法,不能讀取任何實例屬性的值,也不能飲用self的值。


以下是兩段式構造過程中基於上述安全檢查的構造流程展示:

階段1

1:某個指定構造器或便利構造器被調用;

2:完成新實例內存的分配,但此時內存還沒有被初始化;

3:指定構造器確保其所在類引入的所有存儲型屬性都已賦初值。存儲型屬性所屬的額內存完成初始化;

4:指定構造器將調用父類的構造器,完成父類屬性的初始化;

5:這個調用父類構造器的過程沿着構造器鏈一直往上執行,直到到達構造器鏈的最頂部;

6:當到達了構造器鏈最頂部,且已確保所有實例包含的存儲型屬性都已賦值,這個實例的內存被認爲已經完全初始化,此時階段1完成。


階段2

1:從頂部構造器一直往下,每個構造器鏈中類的指定構造器都有機會進一步定製實例。構造器此時可以訪問self、修改它的屬性並調用實例方法等等;

2:最終,任意構造器鏈中的便利構造器可以有機會定製實例和使用self;









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