Swift3.0P1 語法指南——構造器

原檔:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203html

參考:http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.htmlios

一、構造過程(Initialization)

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

構造過程是經過定義構造器(Initializers)來實現的,這些構造器能夠看作是用來建立特定類型實例的特殊方法。與 Objective-C 中的構造器不一樣,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。安全

類的實例也能夠經過定義析構器(deinitializer)在實例釋放以前執行特定的清除工做。閉包

 二、設置存儲屬性的初始值

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

能夠在構造器中爲存儲型屬性賦初值,也能夠在定義屬性時爲其設置默認值。框架

注意:在構造器設置存儲屬性的值,或者定義存儲屬性默認值,這些都會直接設置值,不會調用屬性觀察器。ide

(1)構造器

構造器在建立某特定類型的新實例時調用。最簡形式以下:函數

1 init() {
2     // perform some initialization here
3 }

下面是一個結構體的初始化示例:工具

1 struct Fahrenheit {
2     var temperature: Double
3     init() {
4         temperature = 32.0
5     }
6 }
7 var f = Fahrenheit()
8 print("The default temperature is \(f.temperature)° Fahrenheit")
9 // prints "The default temperature is 32.0° Fahrenheit"

(2)默認初始值

能夠在屬性聲明時爲其設置默認值。

注意:若是一個屬性老是使用同一個初始值,能夠爲其設置一個默認值,而不是在構造器中賦值。儘管它們實現的效果是同樣的,只不過默認值將屬性的初始化和屬性的聲明結合的更緊密。使用默認值能讓你的構造器更簡潔、更清晰,且能經過默認值自動推導出屬性的類型;同時,它也能讓你充分利用默認構造器、構造器繼承等特性。

1 struct Fahrenheit {
2     var temperature = 32.0
3 }

三、自定義構造過程

能夠經過輸入參數和可選屬性類型來定義構造過程,也能夠在構造過程當中修改常量屬性。

(1)構造參數

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

 1 struct Celsius {
 2     var temperatureInCelsius: Double
 3     init(fromFahrenheit fahrenheit: Double) {
 4         temperatureInCelsius = (fahrenheit - 32.0) / 1.8
 5     }
 6     init(fromKelvin kelvin: Double) {
 7         temperatureInCelsius = kelvin - 273.15
 8     }
 9 }
10 let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
11 // boilingPointOfWater.temperatureInCelsius is 100.0
12 let freezingPointOfWater = Celsius(fromKelvin: 273.15)
13 // freezingPointOfWater.temperatureInCelsius is 0.0

(2)參數名和變量標籤(Parameter Names and Argument Labels)

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

然而,構造器並不像函數和方法那樣在括號前有一個可辨別的名字。因此在調用構造器時,主要經過構造器中的參數名和類型來肯定須要調用的構造器。

若是你在定義構造器時沒有提供參數的外部名,Swift 會爲每一個構造器的參數自動生成一個跟內部名相同的外部名。

 1 struct Color {
 2     let red, green, blue: Double
 3     init(red: Double, green: Double, blue: Double) {
 4         self.red   = red
 5         self.green = green
 6         self.blue  = blue
 7     }
 8     init(white: Double) {
 9         red   = white
10         green = white
11         blue  = white
12     }
13 }

兩個構造器均可以用來建立實例:

1 let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
2 let halfGray = Color(white: 0.5)

注意,不能不用外部名來調用這些構造器,若是外部名被定義,就必定要使用,省略將會形成編譯錯誤:

1 let veryGreen = Color(0.0, 1.0, 0.0)
2 // this reports a compile-time error - external names are required

(3)沒有外部名的構造器

若是使用構造器時不想用外部名,則定義參數的時候用_代替顯式外部名,覆蓋默認的外部名。

 1 struct Celsius {
 2     var temperatureInCelsius: Double
 3     init(fromFahrenheit fahrenheit: Double) {
 4         temperatureInCelsius = (fahrenheit - 32.0) / 1.8
 5     }
 6     init(fromKelvin kelvin: Double) {
 7         temperatureInCelsius = kelvin - 273.15
 8     }
 9     init(_ celsius: Double) {
10         temperatureInCelsius = celsius
11     }
12 }
13 let bodyTemperature = Celsius(37.0)
14 // bodyTemperature.temperatureInCelsius is 37.0

(4)可選屬性類型

可選類型的屬性將自動初始化爲空nil,表示這個屬性在構造過程當中尚未值。

 1 class SurveyQuestion {
 2     var text: String
 3     var response: String?
 4     init(text: String) {
 5         self.text = text
 6     }
 7     func ask() {
 8         print(text)
 9     }
10 }
11 let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
12 cheeseQuestion.ask()
13 // prints "Do you like cheese?"
14 cheeseQuestion.response = "Yes, I do like cheese."

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

(5)構造過程當中給常量屬性賦值

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

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

 1 class SurveyQuestion {
 2     let text: String
 3     var response: String?
 4     init(text: String) {
 5         self.text = text
 6     }
 7     func ask() {
 8         print(text)
 9     }
10 }
11 let beetsQuestion = SurveyQuestion(text: "How about beets?")
12 beetsQuestion.ask()
13 // prints "How about beets?"
14 beetsQuestion.response = "I also like beets. (But not with cheese.)"

 四、默認構造器

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

1 class ShoppingListItem {
2     var name: String?
3     var quantity = 1
4     var purchased = false
5 }
6 var item = ShoppingListItem()

因爲ShoppingListItem類中的全部屬性都有默認值,且它是沒有父類的基類,它將自動得到一個能夠爲全部屬性設置默認值的默認構造器(儘管代碼中沒有顯式爲name屬性設置默認值,但因爲name是String?類型,它將默認設置爲nil)。上面例子中使用默認構造器創造了一個ShoppingListItem類的實例(使用ShoppingListItem()形式的構造器語法),並將其賦值給變量item

(1)結構體類型的逐一成員構造器

若是結構體對全部存儲型屬性提供了默認值且自身沒有提供定製的構造器,它們能自動得到一個逐一成員構造器。

和默認構造器不一樣,結構體接收逐一成員構造器即便它有些存儲屬性尚未初始值。

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

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

五、值類型的構造器代理(Initializer Delegation for Value Types)

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

構造器代理的實現規則和形式在值類型和類類型中有所不一樣。

值類型(結構體和枚舉類型)不支持繼承,因此構造器代理的過程相對簡單,由於它們只能代理給自己提供的其它構造器。

類則不一樣,它能夠繼承自其它類,這意味着類有責任保證其全部繼承的存儲型屬性在構造時也能正確的初始化。

對於值類型,在寫自定義的構造器時能夠用self.init引用其它的屬於相同值類型的構造器。而且你只能在構造器內部調用self.init

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

注意:若是你想經過默認構造器、逐一對象構造器以及你本身定製的構造器爲值類型建立實例,咱們建議你將本身定製的構造器寫到擴展(extension)中,而不是跟值類型定義混在一塊兒。

1 struct Size {
2     var width = 0.0, height = 0.0
3 }
4 struct Point {
5     var x = 0.0, y = 0.0
6 }

你能夠經過如下三種方式爲Rect建立實例--使用默認的0值來初始化originsize屬性;使用特定的originsize實例來初始化;使用特定的centersize來初始化。這三種方式對應的構造器:

 1 struct Rect {
 2     var origin = Point()
 3     var size = Size()
 4     init() {}
 5     init(origin: Point, size: Size) {
 6         self.origin = origin
 7         self.size = size
 8     }
 9     init(center: Point, size: Size) {
10         let originX = center.x - (size.width / 2)
11         let originY = center.y - (size.height / 2)
12         self.init(origin: Point(x: originX, y: originY), size: size)
13     }
14 }

第一個Rect構造器init(),在功能上跟沒有自定義構造器時自動得到的默認構造器是同樣的。調用這個構造器將返回一個Rect實例,它的originsize屬性都使用定義時的默認值Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

1 let basicRect = Rect()
2 // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

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

1 let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
2     size: Size(width: 5.0, height: 5.0))
3 // originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

第三個Rect構造器init(center:size:)先經過centersize的值計算出origin的座標。而後再調用(或代理給)init(origin:size:)構造器來將新的originsize值賦值到對應的屬性中:

1 let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
2                       size: Size(width: 3.0, height: 3.0))
3 // centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)


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

六、類的繼承和構造過程

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

Swift 提供了兩種類型的類構造器來確保全部類實例中存儲型屬性都能得到初始值,分別是指定構造器和便利構造器。

(1)指定構造器(Designated Initializers)和便利構造器(Convenience Initializers)

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

每個類都必須擁有至少一個指定構造器。在某些狀況下,許多類經過繼承了父類中的指定構造器而知足了這個條件。

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

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

(2)指定構造器和便利構造器語法

指定構造器的語法和值類型的通常構造器相同:

1 init(parameters) {
2     statements
3 }

便利構造器則須要在init關鍵字前面加上convenience關鍵字:

1 convenience init(parameters) {
2     statements
3 }

(3)類類型的構造器代理(Initializer Delegation for Class Types)

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

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

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

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

一個更方便記憶的方法是:

  • 指定構造器必須老是向上代理
  • 便利構造器必須老是橫向代理

注意:這些規則不會影響使用時,如何用類去建立實例。這些規則只在實現類的定義時有影響

 

(4)兩段式構造過程(Two-Phase Initialization)

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

注意:Swift的兩段式構造過程跟 Objective-C 中的構造過程相似。最主要的區別在於階段 1,Objective-C 給每個屬性賦值0或空值(好比說0nil)。Swift 的構造流程則更加靈活,它容許你設置定製的初始值,並自如應對某些屬性不能以0nil做爲合法默認值的狀況。

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

安全檢查 1

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

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

安全檢查 2

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

安全檢查 3

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

安全檢查 4

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

 

類實例在第一階段結束之前並非徹底有效,僅能訪問屬性和調用方法,一旦完成第一階段,該實例纔會聲明爲有效實例。

如下是兩段式構造過程: 

階段 1

  • 某個指定構造器或便利構造器被調用;
  • 完成新實例內存的分配,但此時內存尚未被初始化;
  • 指定構造器確保其所在類引入的全部存儲型屬性都已賦初值。存儲型屬性所屬的內存完成初始化;
  • 指定構造器將調用父類的構造器,完成父類屬性的初始化;
  • 這個調用父類構造器的過程沿着構造器鏈一直往上執行,直到到達構造器鏈的最頂部;
  • 當到達了構造器鏈最頂部,且已確保全部實例包含的存儲型屬性都已經賦值,這個實例的內存被認爲已經徹底初始化。此時階段1完成。

階段 2

  • 從頂部構造器鏈一直往下,每一個構造器鏈中類的指定構造器都有機會進一步定製實例。構造器此時能夠訪問self、修改它的屬性並調用實例方法等等。
  • 最終,任意構造器鏈中的便利構造器能夠有機會定製實例和使用self

(5)構造器的繼承和重寫

跟 Objective-C 中的子類不一樣,Swift 中的子類不會默認繼承父類的構造器。Swift 的這種機制能夠防止一個父類的簡單構造器被一個更專業的子類繼承,並被錯誤的用來建立子類的實例。

注意: 父類的構造器僅在肯定和安全的狀況下被繼承。

假如你但願自定義的子類中能實現一個或多個跟父類相同的構造器,也許是爲了完成一些定製的構造過程,你能夠在你定製的子類中提供和重寫與父類相同的構造器。

當你寫一個和父類指定構造器相同的子類構造器時,你須要重寫這個指定的構造器。須在定義子類構造器時帶上override修飾符。即便你重寫系統提供的默認構造器也須要帶上override修飾符。

不管是重寫屬性,方法或者是subscript,只要含有override修飾符就會去檢查父類是否有相匹配的重寫指定構造器和驗證重寫構造器參數。

 注意:當你重寫一個父類指定構造器時,你老是須要寫override修飾符,即便你的子類構造器的實現是一個便利構造器。

相反地,若是你寫了一個和父類便利構造器相匹配的子類構造器,子類都不能直接調用父類的便利構造器,所以,嚴格來講,子類沒有複寫父類的便利構造器,所以,這裏沒必要加override修飾符。

下面是一個基類:

1 class Vehicle {
2     var numberOfWheels = 0
3     var description: String {
4         return "\(numberOfWheels) wheel(s)"
5     }
6 }

Vehicle類只爲存儲型屬性提供默認值,而不自定義構造器。所以,它會自動生成一個默認構造器,默認構造器一般在類中是指定構造器,它能夠用於建立屬性叫numberOfWheels值爲0Vehicle實例。

1 let vehicle = Vehicle()
2 print("Vehicle: \(vehicle.description)")
3 // Vehicle: 0 wheel(s)

下面來定義一個子類:

1 class Bicycle: Vehicle {
2     override init() {
3         super.init()
4         numberOfWheels = 2
5     }
6 }

Bicycle類定製了一個構造器init(),這個構造器和父類的指定構造器匹配,所以要加上override關鍵字。

Bicycle的構造器init()一開始調用super.init()方法,這個方法的做用是調用Bicycle的父類Vehicle。這樣能夠確保Bicycle在修改屬性以前它所繼承的屬性numberOfWheels能被Vehicle類初始化。在調用super.init()以後,本來的屬性numberOfWheels被賦值爲2

下面建立一個Bicycle的實例:

1 let bicycle = Bicycle()
2 print("Bicycle: \(bicycle.description)")
3 // Bicycle: 2 wheel(s)

注意:子類能夠在初始化時修改繼承變量屬性,可是不能修改繼承過來的常量屬性。

(6)自動構造器的繼承

如上所述,子類不會默認繼承父類的構造器。可是若是特定條件能夠知足,父類構造器是能夠被自動繼承的。在實踐中,這意味着對於許多常見場景你沒必要重寫父類的構造器,而且在儘量安全的狀況下以最小的代價來繼承父類的構造器。

假設要爲子類中引入的任意新屬性提供默認值,適用如下2個規則:

規則 1

若是子類沒有定義任何指定構造器,它將自動繼承全部父類的指定構造器。

規則 2

若是子類提供了全部父類指定構造器的實現--無論是經過規則1繼承過來的,仍是經過自定義實現的--它將自動繼承全部父類的便利構造器。

即便你在子類中添加了更多的便利構造器,這兩條規則仍然適用。

注意:子類能夠經過知足規則2的方式,使用子類便利構造器來實現父類的指定構造器。

八、指定構造器和便利構造器的操做

下面展現指定構造器、便利構造器和自動構造器繼承步驟。包括了FoodRecipeIngredient, and ShoppingListItem三個類的繼承層次結構。

基類Food:

1 class Food {
2     var name: String
3     init(name: String) {
4         self.name = name
5     }
6     convenience init() {
7         self.init(name: "[Unnamed]")
8     }
9 }

它的構造器鏈是:便利構造器init()調用指定構造器init(name:String).

類沒有提供一個默認的逐一成員構造器,因此Food類提供了一個接受單一參數name的指定構造器。這個構造器可使用一個特定的名字來建立新的Food實例:

1 let namedMeat = Food(name: "Bacon")
2 // namedMeat's name is "Bacon"

Food類中的構造器init(name: String)被定義爲一個指定構造器,由於它能確保全部新Food實例的中存儲型屬性都被初始化。Food類沒有父類,因此init(name: String)構造器不須要調用super.init()來完成構造。

Food類一樣提供了一個沒有參數的便利構造器 init()。這個init()構造器爲新食物提供了一個默認的佔位名字,經過代理調用同一類中定義的指定構造器init(name: String)並給參數name傳值[Unnamed]來實現:

1 let mysteryMeat = Food()
2 // mysteryMeat's name is "[Unnamed]"

下面是Food的子類RecipeIngredientRecipeIngredient類構建了食譜中的一味調味劑。它引入了Int類型的數量屬性quantity(以及從Food繼承過來的name屬性),而且定義了兩個構造器來建立RecipeIngredient實例:

 1 class RecipeIngredient: Food {
 2     var quantity: Int
 3     init(name: String, quantity: Int) {
 4         self.quantity = quantity
 5         super.init(name: name)
 6     }
 7     override convenience init(name: String) {
 8         self.init(name: name, quantity: 1)
 9     }
10 }

RecipeIngredient類擁有一個指定構造器init(name: String, quantity: Int),它能夠用來產生新RecipeIngredient實例的全部屬性值。這個構造器一開始先將傳入的quantity參數賦值給quantity屬性,這個屬性也是惟一在RecipeIngredient中新引入的屬性。隨後,構造器將任務向上代理給父類Foodinit(name: String)。這個過程知足兩段式構造過程當中的安全檢查1。

RecipeIngredient也定義了一個便利構造器init(name: String),它只經過name來建立RecipeIngredient的實例。這個便利構造器假設任意RecipeIngredient實例的quantity爲1,因此不須要顯示指明數量便可建立出實例。這個便利構造器的定義可讓建立實例更加方便和快捷,而且避免了使用重複的代碼來建立多個quantity爲 1 的RecipeIngredient實例。這個便利構造器只是簡單的將任務代理給了同一類裏提供的指定構造器。

注意,RecipeIngredient的便利構造器init(name: String)使用了跟Food中指定構造器init(name: String)相同的參數。由於這個便利構造器重寫要父類的指定構造器init(name: String),必須在前面使用override關鍵字。

在這個例子中,RecipeIngredient的父類是Food,它有一個便利構造器init()。這個構造器所以也被RecipeIngredient繼承。這個繼承的init()函數版本跟Food提供的版本是同樣的,除了它是將任務代理給RecipeIngredient版本的init(name: String)而不是Food提供的版本。

全部的這三種構造器均可以用來建立新的RecipeIngredient實例:

1 let oneMysteryItem = RecipeIngredient()
2 let oneBacon = RecipeIngredient(name: "Bacon")
3 let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

下面是RecipeIngredient的子類:ShoppingListItem

購物單中的每一項老是從unpurchased未購買狀態開始的。爲了展示這一事實,ShoppingListItem引入了一個布爾類型的屬性purchased,它的默認值是falseShoppingListItem還添加了一個計算型屬性description,它提供了關於ShoppingListItem實例的一些文字描述:

1 class ShoppingListItem: RecipeIngredient {
2     var purchased = false
3     var description: String {
4         var output = "\(quantity) x \(name)"
5         output += purchased ? "" : ""
6         return output
7     }
8 }

注意:ShoppingListItem沒有定義構造器來爲purchased提供初始化值,這是由於任何添加到購物單的項的初始狀態老是未購買。

因爲它爲本身引入的全部屬性都提供了默認值,而且本身沒有定義任何構造器,ShoppingListItem將自動繼承父類中全部的指定構造器和便利構造器。

 1 var breakfastList = [
 2     ShoppingListItem(),
 3     ShoppingListItem(name: "Bacon"),
 4     ShoppingListItem(name: "Eggs", quantity: 6),
 5 ]
 6 breakfastList[0].name = "Orange juice"
 7 breakfastList[0].purchased = true
 8 for item in breakfastList {
 9     print(item.description)
10 }
11 // 1 x Orange juice ✔
12 // 1 x Bacon ✘
13 // 6 x Eggs ✘

九、可失敗的構造器

若是一個類,結構體或枚舉類型的對象,在構造自身的過程當中有可能失敗,則爲其定義一個可失敗構造器是很是有用的。這裏所指的「失敗」是指,如給構造器傳入無效的參數值,或缺乏某種所需的外部資源,又或是不知足某種必要的條件等。

爲了妥善處理這種構造過程當中可能會失敗的狀況。你能夠在一個類,結構體或是枚舉類型的定義中,添加一個或多個可失敗構造器。其語法爲在init關鍵字後面加添問號(init?)

注意: 可失敗構造器的參數名和參數類型,不能與其它非可失敗構造器的參數名,及其類型相同

可失敗構造器,在構建對象的過程當中,建立一個其自身類型爲可選類型的對象。你經過return nil 語句,來代表可失敗構造器在何種狀況下「失敗」。

注意: 嚴格來講,構造器都不支持返回值。由於構造器自己的做用,只是爲了能確保對象自身能被正確構建。因此即便你在代表可失敗構造器,失敗的這種狀況下,用到了return nil,也不要在代表成功的這種狀況下,使用關鍵字 return。

1 struct Animal {
2     let species: String
3     init?(species: String) {
4         if species.isEmpty { return nil }
5         self.species = species
6     }
7 }

該結構體還定義了一個,帶一個String類型參數species的,可失敗構造器。這個可失敗構造器,被用來檢查傳入的參數是否爲一個空字符串,若是爲空字符串,則該可失敗構造器,構建對象失敗,不然成功。

能夠經過該可失敗構造器來構建一個Animal的對象,並檢查其構建過程是否成功:

1 let someCreature = Animal(species: "Giraffe")
2 // someCreature is of type Animal?, not Animal
3  
4 if let giraffe = someCreature {
5     print("An animal was initialized with a species of \(giraffe.species)")
6 }
7 // prints "An animal was initialized with a species of Giraffe"

若是給該可失敗構造器傳入一個空字符串做爲其參數,則該可失敗構造器失敗。

1 let anonymousCreature = Animal(species: "")
2 // anonymousCreature is of type Animal?, not Animal
3  
4 if anonymousCreature == nil {
5     print("The anonymous creature could not be initialized")
6 }
7 // prints "The anonymous creature could not be initialized"

注意:空字符串("")和一個值爲nil的可選類型的字符串是兩個徹底不一樣的概念。上例中的空字符串("")實際上是一個有效的,非可選類型的字符串。對於Animal這個類的species屬性來講,它更適合有一個具體的值,而不是空字符串。所以,用空字符串來判定構造失敗。

(1)枚舉類型的可失敗構造器

能夠經過構造一個帶一個或多個參數的可失敗構造器來獲取枚舉類型中特定的枚舉成員。參數不知足你所指望的條件時,構造失敗。

 1 enum TemperatureUnit {
 2     case Kelvin, Celsius, Fahrenheit
 3     init?(symbol: Character) {
 4         switch symbol {
 5         case "K":
 6             self = .Kelvin
 7         case "C":
 8             self = .Celsius
 9         case "F":
10             self = .Fahrenheit
11         default:
12             return nil
13         }
14     }
15 }

你能夠經過給該可失敗構造器傳遞合適的參數來獲取這三個枚舉成員中相匹配的其中一個枚舉成員。若是不匹配,則構造失敗:

 1 let fahrenheitUnit = TemperatureUnit(symbol: "F")
 2 if fahrenheitUnit != nil {
 3     print("This is a defined temperature unit, so initialization succeeded.")
 4 }
 5 // prints "This is a defined temperature unit, so initialization succeeded."
 6  
 7 let unknownUnit = TemperatureUnit(symbol: "X")
 8 if unknownUnit == nil {
 9     print("This is not a defined temperature unit, so initialization failed.")
10 }
11 // prints "This is not a defined temperature unit, so initialization failed."

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

帶原始值的枚舉類型會自帶一個可失敗構造器init?(rawValue:),該可失敗構造器有一個名爲rawValue的默認參數,其類型和枚舉類型的原始值類型一致,若是該參數的值可以和枚舉類型成員所帶的原始值匹配,則該構造器構造一個帶此原始值的枚舉成員,不然構造失敗。

 1 enum TemperatureUnit: Character {
 2     case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
 3 }
 4  
 5 let fahrenheitUnit = TemperatureUnit(rawValue: "F")
 6 if fahrenheitUnit != nil {
 7     print("This is a defined temperature unit, so initialization succeeded.")
 8 }
 9 // prints "This is a defined temperature unit, so initialization succeeded."
10  
11 let unknownUnit = TemperatureUnit(rawValue: "X")
12 if unknownUnit == nil {
13     print("This is not a defined temperature unit, so initialization failed.")
14 }
15 // prints "This is not a defined temperature unit, so initialization failed."

(3)類的可失敗構造器

值類型(結構體或枚舉類型)的可失敗構造器,能夠在構造器的實現內任意位置觸發構造失敗。好比在上述Animal的例子中,結構體的可失敗構造器一開始就觸發失敗,發生在species屬性的值被初始化之前。

而對類而言,類的可失敗構造器只能在全部的存儲屬性被初始化後而且構造器代理調用已經發生後觸發失敗行爲。

1 class Product {
2     let name: String!
3     init?(name: String) {
4         self.name = name
5         if name.isEmpty { return nil }
6     }
7 }

上面定義了一個名爲Product的類。因爲該屬性的值不能爲空字符串,因此咱們加入了可失敗構造器來確保該類知足上述條件。

但因爲Product類不是一個結構體,因此當想要在該類中添加可失敗構造器觸發失敗條件時,必須確保name屬性被初始化。所以咱們把name屬性的String類型改成隱式解析可選類型(String!),全部可選類型都有一個默認的初始值nil,意味着該類的全部屬性都有了有效的初始值。所以Product類的可失敗構造器能夠在一開始就觸發失敗行爲。

name屬性是一個常量屬性,所以,一旦構形成功,name的值必定非空。由於定義成隱式解析可選類型,因此使用時沒必要檢查是否非空:

1 if let bowTie = Product(name: "bow tie") {
2     // no need to check if bowTie.name == nil
3     print("The product's name is \(bowTie.name)")
4 }
5 // prints "The product's name is bow tie"

(4)構造失敗的傳遞

可失敗構造器一樣知足在構造器鏈中所描述的構造規則。其容許在同一類,結構體和枚舉中橫向代理其餘的可失敗構造器。相似的,子類的可失敗構造器也能向上代理基類的可失敗構造器。

不管是向上代理仍是橫向代理,若是你代理的可失敗構造器,在構造過程當中觸發了構造失敗的行爲,整個構造過程都將被當即終止,接下來任何的構造代碼都將不會被執行。

注意: 可失敗構造器也能夠代理調用其它的非可失敗構造器。經過這個方法,你能夠爲已有的構造過程加入構造失敗的條件。

1 class CartItem: Product {
2     let quantity: Int!
3     init?(name: String, quantity: Int) {
4         self.quantity = quantity
5         super.init(name: name)
6         if quantity < 1 { return nil }
7     }
8 }

CartItem的可失敗構造器開始先向上代理調用父類的構造器 init(name:)。這知足了可失敗構造器老是在觸發構造失敗這個行爲前執行構造代理調用這個條件。

若是因爲name的值爲空而致使父類的構造器在構造過程當中失敗。則整個CartIem類的構造過程都將失敗,後面的子類的構造過程都將不會被執行。若是父類構建成功,則繼續運行子類的構造器代碼。

若是你構造了一個CartItem對象,而且該對象的name屬性不爲空以及quantity屬性爲1或者更多,則構形成功:

1 if let twoSocks = CartItem(name: "sock", quantity: 2) {
2     print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
3 }
4 // prints "Item: sock, quantity: 2"

若是你構造一個CartItem對象,其quantity的值0, 則觸發構造失敗的行爲:

1 if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
2     print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
3 } else {
4     print("Unable to initialize zero shirts")
5 }
6 // prints "Unable to initialize zero shirts"

相似的, 若是你構造一個CartItem對象,但name的值爲空, 則基類Product的可失敗構造器將觸發構造失敗的行爲,整個CartItem的構造行爲一樣爲失敗:

1 if let oneUnnamed = CartItem(name: "", quantity: 1) {
2     print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
3 } else {
4     print("Unable to initialize one unnamed product")
5 }
6 // prints "Unable to initialize one unnamed product"

(5)重寫可失敗構造器

能夠用子類的可失敗構造器重寫基類的可失敗構造器。也能夠用子類的非可失敗構造器重寫一個基類的可失敗構造器。這樣作的好處是,即便基類的構造器爲可失敗構造器,但當子類的構造器在構造過程不可能失敗時,咱們也能夠把它修改過來。

須要注意的是,若是你在子類中用一個非可失敗的構造器重寫了父類的可失敗構造器,惟一能夠代理調用父類的可失敗構造器的方式是強制解析可失敗構造器的返回結果。

注意: 你能夠用一個非可失敗構造器重寫一個可失敗構造器,但反過來卻行不通。

下例定義了一個名爲Document的類,這個類中的name屬性容許爲nil和一個非空字符串,但不能是一個空字符串:

 1 class Document {
 2     var name: String?
 3     // this initializer creates a document with a nil name value
 4     init() {}
 5     // this initializer creates a document with a non-empty name value
 6     init?(name: String) {
 7         self.name = name
 8         if name.isEmpty { return nil }
 9     }
10 }

下面這個例子,定義了一個Document類的子類AutomaticallyNamedDocument。這個子類重寫了基類的兩個指定構造器。確保了不論在何種狀況下name屬性老是有一個非空字符串的值。

 1 class AutomaticallyNamedDocument: Document {
 2     override init() {
 3         super.init()
 4         self.name = "[Untitled]"
 5     }
 6     override init(name: String) {
 7         super.init()
 8         if name.isEmpty {
 9             self.name = "[Untitled]"
10         } else {
11             self.name = name
12         }
13     }
14 }

在子類的非可失敗構造器的實現中,能夠強制解析構造器,調用父類的可失敗構造器。

1 class UntitledDocument: Document {
2     override init() {
3         super.init(name: "[Untitled]")!
4     }
5 }

這裏,若是super.init的參數name是空字符串,會致使運行時錯誤。

(6)init!可失敗構造器

一般來講,咱們經過在init關鍵字後添加問號的方式來定義一個可失敗構造器,但你也可使用經過在init後面添加驚歎號的方式來定義一個可失敗構造器(init!),該可失敗構造器將會構建一個特定類型的隱式解析可選類型的對象。

你能夠在 init?構造器中代理調用 init!構造器,反之亦然。 你也能夠用 init?重寫 init!,反之亦然。 你還能夠用 init代理調用init!,但這會觸發一個斷言:是否 init! 構造器會觸發構造失敗?

十、必要構造器

在類的構造器前添加 required 修飾符代表全部該類的子類都必須實現該構造器:

1 class SomeClass {
2     required init() {
3         // initializer implementation goes here
4     }
5 }

當子類重寫基類的必要構造器時,必須在子類的構造器前一樣添加required修飾符以確保當其它類繼承該子類時,該構造器同爲必要構造器。在重寫基類的必要構造器時,不須要添加override修飾符:

1 class SomeSubclass: SomeClass {
2     required init() {
3         // subclass implementation of the required initializer goes here
4     }
5 }

注意:若是子類繼承的構造器能知足必要構造器的需求,則你無需顯式地在子類中提供必要構造器的實現。

十一、用函數或閉包來設置默認屬性值

若是某個存儲型屬性的默認值須要特別的定製或準備,你就可使用閉包或全局函數來爲其屬性提供定製的默認值。每當某個屬性所屬的新類型實例建立時,對應的閉包或函數會被調用,而它們的返回值會當作默認值賦值給這個屬性。

這種類型的閉包或函數通常會建立一個跟屬性類型相同的臨時變量,而後修改它的值以知足預期的初始狀態,最後將這個臨時變量的值做爲屬性的默認值進行返回。

下面是閉包產生屬性默認值的框架:

1 class SomeClass {
2     let someProperty: SomeType = {
3         // create a default value for someProperty inside this closure
4         // someValue must be of the same type as SomeType
5         return someValue
6     }()
7 }

注意閉包結尾的大括號後面接了一對空的小括號。這是用來告訴 Swift 須要馬上執行此閉包。若是你忽略了這對括號,至關因而將閉包自己做爲值賦值給了屬性,而不是將閉包的返回值賦值給屬性。

注意:若是你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都尚未初始化。這意味着你不可以在閉包裏訪問其它的屬性,就算這個屬性有默認值也不容許。一樣,你也不能使用隱式的self屬性,或者調用其它的實例方法。

 1 struct Checkerboard {
 2     let boardColors: [Bool] = {
 3         var temporaryBoard = [Bool]()
 4         var isBlack = false
 5         for i in 1...10 {
 6             for j in 1...10 {
 7                 temporaryBoard.append(isBlack)
 8                 isBlack = !isBlack
 9             }
10             isBlack = !isBlack
11         }
12         return temporaryBoard
13     }()
14     func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
15         return boardColors[(row * 10) + column]
16     }
17 }

每當一個新的Checkerboard實例建立時,對應的賦值閉包會執行,一系列值會被計算出來做爲默認值賦值給boardColors,並能夠通squareIsBlackAtRow這個工具函數來查詢。

1 let board = Checkerboard()
2 print(board.squareIsBlackAtRow(0, column: 1))
3 // prints "true"
4 print(board.squareIsBlackAtRow(9, column: 9))
5 // prints "false"
相關文章
相關標籤/搜索