【Swift學習】Swift編程之旅---構造方法(十八)

  初始化是爲了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包括爲每一個存儲的屬性設置一個初始值,而後執行新實例所需的任何其餘設置或初始化。數組

 
  初始化是經過定義構造器(Initializers)來實現的,這些構造器能夠看作是用來建立特定類型實例的特殊方法。與 Objective-C 中的構造器不一樣,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。
 
  類實例也能夠經過定義析構器(deinitializer)在類實例釋放以前執行特定的清除工做。
 
  存儲型屬性的初始化
類和結構體在實例建立時,必須爲全部存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。
 
你能夠在構造器中爲存儲型屬性賦初值,也能夠在定義屬性時爲其設置默認值。如下章節將詳細介紹這兩種方法。
 
注意:當你爲存儲型屬性設置默認值或者在構造器中爲其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀測器(property observers)。
 
構造器
構造器在建立某特定類型的新實例時調用。它的最簡形式相似於一個不帶任何參數的實例方法,以init關鍵字命名。
 
init() { // perform some initialization here
}

 

下面例子中定義了一個用來保存華氏溫度的結構體Fahrenheit,它擁有一個Double類型的存儲型屬性temperature:
struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() println("The default temperature is \(f.temperature)° Fahrenheit") // 輸出 "The default temperature 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.0 
let 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, 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 } }

 

每當你建立一個新的Color實例,你都須要經過三種顏色的外部參數名來傳值,並調用構造器。
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0) let halfGray = Color(white: 0.5)

 

注意,若是不經過外部參數名字傳值,你是無法調用這個構造器的。只要構造器定義了某個外部參數名,你就必須使用它,忽略它將致使編譯錯誤
let veryGreen = Color(0.0, 1.0, 0.0) // 報編譯時錯誤,須要外部名稱 

 

  無外部名稱參數的構造器安全

  若是你不但願爲構造器的某個參數提供外部名字,你可使用下劃線_來顯示描述它的外部名,以此覆蓋上面所說的默認行爲。閉包

struct Celsius { var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double) { temperatureInCelsius = kelvin - 273.15 } init(_ celsius: Double) { temperatureInCelsius = celsius } } let bodyTemperature = Celsius(37.0) // bodyTemperature.temperatureInCelsius is 37.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 like cheese?" 
cheeseQuestion.response = "Yes, I do like 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 about beets?" 
beetsQuestion.response = "I also like beets. (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:)的功能,是更方便、更清晰和更直觀的方法。
 
注意:若是你想用另一種不須要本身定義init()和init(origin:size:)的方式來實現這個例子,請參考擴展。
 
  類的繼承和構造過程
類裏面的全部存儲型屬性--包括全部繼承自父類的屬性--都必須在構造過程當中設置初始值。
 
Swift 提供了兩種類型的類構造器來確保全部類實例中存儲型屬性都能得到初始值,它們分別是指定構造器和便利構造器。
 
  指定構造器和便利構造器
指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的全部屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。
指定構造器實現

init(parameters) {
    statements
}

 

便利構造器實現經過在init關鍵字前面加convenience關鍵字app

convenience init(parameters) {
    statements
}

 

每個類都必須擁有至少一個指定構造器。在某些狀況下,許多類經過繼承了父類中的指定構造器而知足了這個條件。
 
便利構造器是類中比較次要的、輔助型的構造器。你能夠定義便利構造器來調用同一個類中的指定構造器,併爲其參數提供默認值。你也能夠定義便利構造器來建立一個特殊用途或特定輸入的實例。
 
你應當只在必要的時候爲類提供便利構造器,比方說某種狀況下經過使用便利構造器來快捷調用某個指定構造器,可以節省更多開發時間並讓類的構造過程更清晰。
 
  構造器鏈
爲了簡化指定構造器和便利構造器之間的調用關係,Swift 採用如下三條規則來限制構造器之間的代理調用:
 
規則 1
指定構造器必須調用其直接父類的的指定構造器。
 
規則 2
便利構造器必須調用同一類中定義的其它構造器。
 
規則 3
便利構造器必須最終以調用一個指定構造器結束。
 
一個更方便記憶的方法是:
 
指定構造器必須老是向上代理
便利構造器必須老是橫向代理
 
這些規則能夠經過下面圖例來講明:
如圖所示,父類中包含一個指定構造器和兩個便利構造器。其中一個便利構造器調用了另一個便利構造器,然後者又調用了惟一的指定構造器。這知足了上面提到的規則2和3。這個父類沒有本身的父類,因此規則1沒有用到。
 
子類中包含兩個指定構造器和一個便利構造器。便利構造器必須調用兩個指定構造器中的任意一個,由於它只能調用同一個類裏的其餘構造器。這知足了上面提到的規則2和3。而兩個指定構造器必須調用父類中惟一的指定構造器,這知足了規則1。
 
注意:這些規則不會影響使用時,如何用類去建立實例。任何上圖中展現的構造器均可以用來完整建立對應類的實例。這些規則只在實現類的定義時有影響。
 
下面圖例中展現了一種更復雜的類層級結構。它演示了指定構造器是如何在類層級中充當「管道」的做用,在類的構造器鏈上簡化了類之間的內部關係。
 
  兩段式構造過程
Swift 中類的構造過程包含兩個階段。第一個階段,每一個存儲型屬性經過引入它們的類的構造器來設置初始值。當每個存儲型屬性值被肯定後,第二階段開始,它給每一個類一次機會在新實例準備使用以前進一步定製它們的存儲型屬性。
 
兩段式構造過程的使用讓構造過程更安全,同時在整個類層級結構中給予了每一個類徹底的靈活性。兩段式構造過程能夠防止屬性值在初始化以前被訪問;也能夠防止屬性被另一個構造器意外地賦予不一樣的值。
 
注意:Swift的兩段式構造過程跟 Objective-C 中的構造過程相似。最主要的區別在於階段 1,Objective-C 給每個屬性賦值0或空值(好比說0或nil)。Swift 的構造流程則更加靈活,它容許你設置定製的初始值,並自如應對某些屬性不能以0或nil做爲合法默認值的狀況。
 
Swift 編譯器將執行 4 種有效的安全檢查,以確保兩段式構造過程能順利完成:
 
  安全檢查 1
指定構造器必須保證它所在類引入的全部屬性都必須先初始化完成,以後才能將其它構造任務向上代理給父類中的構造器。
 
如上所述,一個對象的內存只有在其全部存儲型屬性肯定以後才能徹底初始化。爲了知足這一規則,指定構造器必須保證它所在類引入的屬性在它往上代理以前先完成初始化。
 
  安全檢查 2
指定構造器必須先向上代理調用父類構造器,而後再爲繼承的屬性設置新值。若是沒這麼作,指定構造器賦予的新值將被父類中的構造器所覆蓋。
 
  安全檢查 3
便利構造器必須先代理調用同一類中的其它構造器,而後再爲任意屬性賦新值。若是沒這麼作,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋。
 
  安全檢查 4
構造器在第一階段構造完成以前,不能調用任何實例方法、不能讀取任何實例屬性的值,也不能引用self的值。
 
如下是兩段式構造過程當中基於上述安全檢查的構造流程展現:
 
  階段 1
某個指定構造器或便利構造器被調用;
完成新實例內存的分配,但此時內存尚未被初始化;
指定構造器確保其所在類引入的全部存儲型屬性都已賦初值。存儲型屬性所屬的內存完成初始化;
指定構造器將調用父類的構造器,完成父類屬性的初始化;
這個調用父類構造器的過程沿着構造器鏈一直往上執行,直到到達構造器鏈的最頂部;
當到達了構造器鏈最頂部,且已確保全部實例包含的存儲型屬性都已經賦值,這個實例的內存被認爲已經徹底初始化。此時階段1完成。
 
  階段 2
從頂部構造器鏈一直往下,每一個構造器鏈中類的指定構造器都有機會進一步定製實例。構造器此時能夠訪問self、修改它的屬性並調用實例方法等等。
最終,任意構造器鏈中的便利構造器能夠有機會定製實例和使用self。
 
下圖展現了在假定的子類和父類之間構造的階段1: · 
 
在這個例子中,構造過程從對子類中一個便利構造器的調用開始。這個便利構造器此時無法修改任何屬性,它把構造任務代理給同一類中的指定構造器。
 
如安全檢查1所示,指定構造器將確保全部子類的屬性都有值。而後它將調用父類的指定構造器,並沿着造器鏈一直往上完成父類的構建過程。
 
父類中的指定構造器確保全部父類的屬性都有值。因爲沒有更多的父類須要構建,也就無需繼續向上作構建代理。
 
一旦父類中全部屬性都有了初始值,實例的內存被認爲是徹底初始化,而階段1也已完成。
 
如下展現了相同構造過程的階段2:
父類中的指定構造器如今有機會進一步來定製實例(儘管它沒有這種必要)。
 
一旦父類中的指定構造器完成調用,子類的構指定構造器能夠執行更多的定製操做(一樣,它也沒有這種必要)。
 
最終,一旦子類的指定構造器完成調用,最開始被調用的便利構造器能夠執行更多的定製操做。
 
  構造器的繼承和重載
跟 Objective-C 中的子類不一樣,Swift 中的子類不會默認繼承父類的構造器。Swift 的這種機制能夠防止一個父類的簡單構造器被一個更專業的子類繼承,並被錯誤的用來建立子類的實例。
 
假如你但願自定義的子類中能實現一個或多個跟父類相同的構造器--也許是爲了完成一些定製的構造過程--你能夠在你定製的子類中提供和重載與父類相同的構造器。
 
若是你重載的構造器是一個指定構造器,你能夠在子類裏重載它的實現,並在自定義版本的構造器中調用父類版本的構造器。
 
若是你重載的構造器是一個便利構造器,你的重載過程必須經過調用同一類中提供的其它指定構造器來實現。
 
注意:與方法、屬性和下標不一樣,在重載構造器時你沒有必要使用關鍵字override。
 
  自動構造器的繼承
如上所述,子類不會默認繼承父類的構造器。可是若是特定條件能夠知足,父類構造器是能夠被自動繼承的。在實踐中,這意味着對於許多常見場景你沒必要重載父類的構造器,而且在儘量安全的狀況下以最小的代價來繼承父類的構造器。
 
假設要爲子類中引入的任意新屬性提供默認值,請遵照如下2個規則:
 
  規則 1
若是子類沒有定義任何指定構造器,它將自動繼承全部父類的指定構造器。
 
  規則 2
若是子類提供了全部父類指定構造器的實現--無論是經過規則1繼承過來的,仍是經過自定義實現的--它將自動繼承全部父類的便利構造器。
 
即便你在子類中添加了更多的便利構造器,這兩條規則仍然適用。
 
注意:子類能夠經過部分知足規則2的方式,使用子類便利構造器來實現父類的指定構造器。
 
  指定構造器和便利構造器實戰
接下來的例子將在實戰中展現指定構造器、便利構造器和自動構造器的繼承。它定義了包含三個類Food、RecipeIngredient以及ShoppingListItem的類層次結構,並將演示它們的構造器是如何相互做用的。
 
類層次中的基類是Food,它是一個簡單的用來封裝食物名字的類。Food類引入了一個叫作name的String類型屬性,而且提供了兩個構造器來建立Food實例:
class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } } 

 

下圖中展現了Food的構造器鏈:
類沒有提供一個默認的逐一成員構造器,因此Food類提供了一個接受單一參數name的指定構造器。這個構造器可使用一個特定的名字來建立新的Food實例:
let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon」 

 

Food類中的構造器init(name: String)被定義爲一個指定構造器,由於它能確保全部新Food實例的中存儲型屬性都被初始化。Food類沒有父類,因此init(name: String)構造器不須要調用super.init()來完成構造。
 
Food類一樣提供了一個沒有參數的便利構造器 init()。這個init()構造器爲新食物提供了一個默認的佔位名字,經過代理調用同一類中定義的指定構造器init(name: String)並給參數name傳值[Unnamed]來實現:
let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed] 

 

類層級中的第二個類是Food的子類RecipeIngredient。RecipeIngredient類構建了食譜中的一味調味劑。它引入了Int類型的數量屬性quantity(以及從Food繼承過來的name屬性),而且定義了兩個構造器來建立RecipeIngredient實例
class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) } convenience init(name: String) { self.init(name: name, quantity: 1) } } 

 

下圖中展現了RecipeIngredient類的構造器鏈:
RecipeIngredient類擁有一個指定構造器init(name: String, quantity: Int),它能夠用來產生新RecipeIngredient實例的全部屬性值。這個構造器一開始先將傳入的quantity參數賦值給quantity屬性,這個屬性也是惟一在RecipeIngredient中新引入的屬性。隨後,構造器將任務向上代理給父類Food的init(name: String)。這個過程知足兩段式構造過程當中的安全檢查1。
 
RecipeIngredient也定義了一個便利構造器init(name: String),它只經過name來建立RecipeIngredient的實例。這個便利構造器假設任意RecipeIngredient實例的quantity爲1,因此不須要顯示指明數量便可建立出實例。這個便利構造器的定義可讓建立實例更加方便和快捷,而且避免了使用重複的代碼來建立多個quantity爲 1 的RecipeIngredient實例。這個便利構造器只是簡單的將任務代理給了同一類裏提供的指定構造器。
 
注意,RecipeIngredient的便利構造器init(name: String)使用了跟Food中指定構造器init(name: String)相同的參數。儘管RecipeIngredient這個構造器是便利構造器,RecipeIngredient依然提供了對全部父類指定構造器的實現。所以,RecipeIngredient也能自動繼承了全部父類的便利構造器。
 
在這個例子中,RecipeIngredient的父類是Food,它有一個便利構造器init()。這個構造器所以也被RecipeIngredient繼承。這個繼承的init()函數版本跟Food提供的版本是同樣的,除了它是將任務代理給RecipeIngredient版本的init(name: String)而不是Food提供的版本。
 
全部的這三種構造器均可以用來建立新的RecipeIngredient實例:
let oneMysteryItem = RecipeIngredient() let oneBacon = RecipeIngredient(name: "Bacon") let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6) 

 

類層級中第三個也是最後一個類是RecipeIngredient的子類,叫作ShoppingListItem。這個類構建了購物單中出現的某一種調味料。
 
購物單中的每一項老是從unpurchased未購買狀態開始的。爲了展示這一事實,ShoppingListItem引入了一個布爾類型的屬性purchased,它的默認值是false。ShoppingListItem還添加了一個計算型屬性description,它提供了關於ShoppingListItem實例的一些文字描述
class ShoppingListItem: RecipeIngredient { var purchased = false 
    var description: String { var output = "\(quantity) x \(name.lowercaseString)" output += purchased ? " ?" : " ?" 
        return output } } 

 

注意:ShoppingListItem沒有定義構造器來爲purchased提供初始化值,這是由於任何添加到購物單的項的初始狀態老是未購買。
 
因爲它爲本身引入的全部屬性都提供了默認值,而且本身沒有定義任何構造器,ShoppingListItem將自動繼承全部父類中的指定構造器和便利構造器。
 
下圖種展現了全部三個類的構造器鏈:
你可使用所有三個繼承來的構造器來建立ShoppingListItem的新實例:
var breakfastList = [ ShoppingListItem(), ShoppingListItem(name: "Bacon"), ShoppingListItem(name: "Eggs", quantity: 6), ] breakfastList[0].name = "Orange juice" breakfastList[0].purchased = true 
for item in breakfastList { println(item.description) } // 1 x orange juice ? // 1 x bacon ? // 6 x eggs ? 

 

如上所述,例子中經過字面量方式建立了一個新數組breakfastList,它包含了三個新的ShoppingListItem實例,所以數組的類型也能自動推導爲ShoppingListItem[]。在數組建立完以後,數組中第一個ShoppingListItem實例的名字從[Unnamed]修改成Orange juice,並標記爲已購買。接下來經過遍歷數組每一個元素並打印它們的描述值,展現了全部項當前的默認狀態都已按照預期完成了賦值。
 
  Failable Initializers
經過構造器初始化其實是給class或struct,枚舉的每個存儲屬性(參數)提供初始值,進行對象實例化的過程。在一些狀況下,初始化的過程可能會由於參數錯誤是有可能失敗的。經過init?來聲明一個可失敗構造器
注意:你不能定義一個與可失敗構造器的參數和名稱相同的不可失敗構造器。
 
failable初始化建立的類型初始化一個可選值。返回nil表示初始化失敗可觸發的
 
 
使用可失敗構造器可極大程度的統一Swift中的構造對象語法,消除了構造器與工廠方法之間混亂、重複的冗餘語法,使Swift更加簡潔。隨着可失敗構造器這一特性的加入,Swift將對大多數Cocoa中帶NSError參數的工廠初始化方法進行調整,從而增強Swift中構造對象語法的統一性,給開發者帶來更好的開發體驗。
經過閉包和函數來設置屬性的默認值
若是某個存儲型屬性的默認值須要特別的定製或準備,你就可使用閉包或全局函數來爲其屬性提供定製的默認值。每當某個屬性所屬的新類型實例建立時,對應的閉包或函數會被調用,而它們的返回值會當作默認值賦值給這個屬性。
 
這種類型的閉包或函數通常會建立一個跟屬性類型相同的臨時變量,而後修改它的值以知足預期的初始狀態,最後將這個臨時變量的值做爲屬性的默認值進行返回。
 
下面列舉了閉包如何提供默認值的代碼概要:
class SomeClass { let someProperty: SomeType = { // 在這個閉包中給 someProperty 建立一個默認值 // someValue 必須和 SomeType 類型相同 
        return someValue }() } 

 

注意閉包結尾的大括號後面接了一對空的小括號。這是用來告訴 Swift 須要馬上執行此閉包。若是你忽略了這對括號,至關因而將閉包自己做爲值賦值給了屬性,而不是將閉包的返回值賦值給屬性。
 
注意:若是你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都尚未初始化。這意味着你不可以在閉包裏訪問其它的屬性,就算這個屬性有默認值也不容許。一樣,你也不能使用隱式的self屬性,或者調用其它的實例方法。
 
下面例子中定義了一個結構體Checkerboard,它構建了西洋跳棋遊戲的棋盤:
西洋跳棋遊戲在一副黑白格交替的 10x10 的棋盤中進行。爲了呈現這副遊戲棋盤,Checkerboard結構體定義了一個屬性boardColors,它是一個包含 100 個布爾值的數組。數組中的某元素布爾值爲true表示對應的是一個黑格,布爾值爲false表示對應的是一個白格。數組中第一個元素表明棋盤上左上角的格子,最後一個元素表明棋盤上右下角的格子。
 
boardColor數組是經過一個閉包來初始化和組裝顏色值的:
struct Chessboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false
        for i in 1...8 { for j in 1...8 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 8) + column] } }

 

每當一個新的Checkerboard實例建立時,對應的賦值閉包會執行,一系列顏色值會被計算出來做爲默認值賦值給boardColors。上面例子中描述的閉包將計算出棋盤中每一個格子合適的顏色,將這些顏色值保存到一個臨時數組temporaryBoard中,並在構建完成時將此數組做爲閉包返回值返回。這個返回的值將保存到boardColors中,並能夠通squareIsBlackAtRow這個工具函數來查詢。
let board = Chessboard() print(board.squareIsBlackAtRow(0, column: 1)) // Prints "true"
print(board.squareIsBlackAtRow(7, column: 7)) // Prints "false」
相關文章
相關標籤/搜索