標籤:swift ios
構造過程是為了使用某個類、結構體或枚舉類型的執行個體而進行的準備過程。這個過程包含了為執行個體中的每個屬性設定初始值和為其執行必要的準備和初始化任務。
構造過程是通過定義構造器(Initializers)來實現的,這些構造器可以看做是用來建立特定類型執行個體的特殊方法。與 Objective-C 中的構造器不同,Swift 的構造器無需返回值,它們的主要任務是保證新執行個體在第一次使用前完成正確的初始化。
類執行個體也可以通過定義析構器(deinitializer)在類執行個體釋放之前執行特定的清除工作。想瞭解更多關於析構器的內容,請參考析構過程。
儲存型屬性的初始賦值
類和結構體在執行個體建立時,必須為所有儲存型屬性設定合適的初始值。儲存型屬性的值不能處於一個未知的狀態。
你可以在構造器中為儲存型屬性賦初值,也可以在定義屬性時為其設定預設值。以下章節將詳細介紹這兩種方法。
注意:
當你為儲存型屬性設定預設值或者在構造器中為其賦值時,它們的值是被直接設定的,不會觸發任何屬性觀測器(property observers)。
構造器
構造器在建立某特定類型的新執行個體時調用。它的最簡形式類似於一個不帶任何參數的執行個體方法,以關鍵字init命名。
下面例子中定義了一個用來儲存華氏溫度的結構體Fahrenheit,它擁有一個Double類型的儲存型屬性temperature:
struct Fahrenheit { var temperature: Double init() { temperature = 32.0 }} var f = Fahrenheit()println("The default temperature is\(f.temperature)° Fahrenheit")// 輸出 "The defaulttemperature is 32.0° Fahrenheit”
這個結構體定義了一個不帶參數的構造器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)// boilingPointOfWater.temperatureInCelsius是 100.0let freezingPointOfWater =Celsius(fromKelvin: 273.15)//freezingPointOfWater.temperatureInCelsius 是 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:
class SurveyQuestion { var text: String var response: String? init(text: String) { self.text = text } func ask() { println(text) }}let cheeseQuestion = SurveyQuestion(text:"Do you like cheese?")cheeseQuestion.ask()// 輸出 "Do you likecheese?"cheeseQuestion.response = "Yes, I dolike cheese.
調查問題在問題提出之後,我們才能得到回答。所以我們將屬性回答response聲明為String?類型,或者說是可選字串類型optional String。當SurveyQuestion執行個體化時,它將自動賦值為空白nil,表明暫時還不存在此字串。
構造過程中常量屬性的修改
只要在構造過程結束前常量的值能確定,你可以在構造過程中的任意時間點修改常量屬性的值。
注意:
對某個類執行個體來說,它的常量屬性只能在定義它的類的構造過程中修改;不能在子類中修改。
你可以修改上面的SurveyQuestion樣本,用常量屬性替代變數屬性text,指明問題內容text在其建立之後不會再被修改。儘管text屬性現在是常量,我們仍然可以在其類的構造器中修改它的值:
class 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 aboutbeets?"beetsQuestion.response = "I also likebeets. (But not with cheese.)
預設構造器
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。
結構體的逐一成員構造器
除上面提到的預設構造器,如果結構體對所有儲存型屬性提供了預設值且自身沒有提供定製的構造器,它們能自動獲得一個逐一成員構造器。
逐一成員構造器是用來初始化結構體新執行個體裡成員屬性的快捷方法。我們在調用逐一成員構造器時,通過與成員屬性名相同的參數名進行傳值來完成對成員屬性的初始賦值。
下面例子中定義了一個結構體Size,它包含兩個屬性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()// basicRect 的原點是 (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:)的功能,是更方便、更清晰和更直觀的方法。