十四 初始化swift
初始化是類,結構體和枚舉類型實例化的準備階段。這個階段設置這個實例存儲的屬性的初始化數值和作一些使用實例以前的準備以及必需要作的其餘一些設置工做。數組
經過定義構造器(initializers)實現這個實例化過程,也就是建立一個新的具體實例的特殊方法。和Objective-C不同的是,Swift的構造器沒有返回值。它們主要充當的角色是確保這個實例在使用以前能正確的初始化。閉包
類實例也能實現一個析構器(deinitializer),在類實例銷燬以前作一些清理工做。app
一、存儲屬性的初始化ide
類和結構體必須在它們被建立時把它們全部的屬性設置爲合理的值。存儲屬性不能爲不肯定狀態函數
你能夠在構造方法裏面給一個屬性設置一個初始值,或者在定義的時候給屬性設置一個默認值工具
注意:當你對給一個屬性分配一個默認值的時候,它會調用它相對應的初始化方法,這個值是對屬性直接設置的,不會通知它對應的觀察者ui
構造器this
構造器是建立一個具體類型實例的方法。最簡單的構造器就是一個沒有任何參數實例方法,寫做init。spa
在下面的例子定義了一個叫Fahrenheit(華氏度)的新結構體,來儲存轉換成華氏度的溫度。Fahrenheit結構體,有一個屬性,叫temperature(溫度),它的類型爲Double(雙精度浮點數):
struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() print("The default temperature is \(f.temperature)° Fahrenheit")
這個結構體定義了一個單一的構造方法init,它沒有任何參數,它儲存的溫度屬性初始化爲32.0度。(水在華氏度的溫度狀況下的冰點)。
屬性的默認值
如上所述,你能夠在構造器中設置它本身的儲存屬性的初始化值。或者在屬性聲明時,指定屬性的默認值,你指定一個默認的屬性值,會被分配到它定義的初始值。
注意:若是一個屬性經常使用一樣的初始化值 ,提供一個默認值會比在初始化使用一個默認值會更好。
一樣的結果,可是默認值與屬性的初始化在它定義地時候就牢牢地捆綁在一塊兒。很簡單地就能構造器更簡潔,和可讓你從默認值中推斷出這個屬性的類型。
struct Fahrenheit1 { var temperature = 32.0 }
2、自定義初始化(Customizing Initialization)
你能夠根據輸入的參數來自定義初始化過程和可選的屬性類型,或者在初始化的時候修改靜態屬性。
初始化參數
你能夠在構造器定義的時候提供一部分參數,在自定義初始化過程當中定義變量的類型和名稱。
初始化參和函數或者方法參數同樣有着一樣的功能。
在下面的例子中,定義了一個結構體Celsius。儲存了轉換成攝氏度的溫度,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)
第一個構造器只有一個初始化參數,形參(External Parameter Names)fromFahrenheit,和實參(Local Parameter Names)fahrenheit。第二個構造器有一個單一的初始化參數,形參(External Parameter Names)fromKenvin,和實參(Local Parameter Names)kelvin。兩個構造器都把單一的參數轉換爲攝氏度和儲存到一個temperatureInCelsius的屬性.
實參名(Local Parameter Names)和形參名(External Parameter Names)
和函數參數和方法參數同樣,初始化參數擁有在構造器函數體使用的實參,和在調用時使用的形參.
然而,和函數或者方法不一樣,構造器在圓括號前面沒有一個識別函數名稱。所以,構造器參數的名稱和類型,在被調用的時候,很大程度上扮演一個被識別的重要角色。爲此,在構造器中,當你沒有提供形參名時,Swift就會爲每個參數提供一個自動的形參名。這個形參名和實參名相同,就像和以前你寫的每個初始化參數的hash符號同樣。
注意:若是你在構造器中沒有定義形參,提供一個下橫線(_)做爲區分形參和上面說描述的重寫默認行爲。
在下面的例子 ,定義了一個結構體Color,擁有三個靜態屬性red,green和blue。這些屬性儲存了從0.0到1.0的值,這些值表明紅色 ,綠色和藍色的深度。
Color提供了一個構造器,以及三個雙精度(Double)類型的參數:
struct Color { var 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)
可選類型
若是你儲存屬性使用的是自定義的類型在邏輯上容許值爲空-或者他們的值並不在構造器中初始化,或者他們被容許爲空。能夠定義一個可選類型的屬性。可選類型屬性是一個自動初始化值爲nil,表示這個屬性有意在構造器中設置爲「空值」(no value yet)。
在下面的例子中,定義了一個SurveryQuestion類,擁有一個可選的String屬性response。
這個回答在他們調查問題在發佈以前是沒法知道的,因此response定義爲類型String? ,或者叫可選String(optional String)。說明它會被自動分配一個默認值nil,意思爲當surverQuestion初始化時還不存在。
在初始化時修改靜態屬性
當你在設置靜態屬性值時,只要在初始化完成以前,你均可以在初始化時隨時修改靜態屬性。
注意:對於類的實例化,一個靜態屬性只能在初始化時被修改,這個初始化在類定義時已經肯定。
你能夠重寫SurveryQuestion例子,對於問題的text屬性,使用靜態屬性會比動態屬性要好,由於SurveyQuestion實例被建立以後就沒法修改。儘管text屬性如今是靜態的,可是仍然能夠在構造器中被設置:
class SurveyQuestion { let text: String var response: String? init(text: String) { self.text = text } func ask() { print(text) } } let beetsQuestion = SurveyQuestion(text: "How about beets?") beetsQuestion.ask() beetsQuestion.response = "I also like beets. (But not with cheese.)"
三、默認構造器
Swift爲每個結構或者基類提供了默認的構造器,來初始化它們所包含的全部屬性。默認構造器將會建立一個新的實例而後將它們的屬性設置爲默認值。
下面的例子定義了一個叫ShoppingListItem的類,包含了名稱,數量和是否已購買的屬性,將會被用在購物清單中:
class ShoppingListItem { var name: String? var quantity = 1 var purchased = false } var item = ShoppingListItem()
由於ShoppingListItem類中全部的屬性都有默認值,而且這個類是一個沒有父類的基類,因此它默認擁有一個會將全部包含的屬性設置爲初始值的默認構造器。好比在這個例子中name屬性是一個可選String屬性,它會被默認設置爲nil,儘管在代碼中沒有指明。上面的例子使用默認構造器建立了一個ShoppingListItem類,記作ShoppingListItem(),而後將它賦值給了變量item。
結構類型的成員逐一構造器
除了上面提到的默認構造器以外,結構類型還有另一種成員逐一完成初始化的構造器,能夠在定義結構的時候直接指定每一個屬性的初始值。
成員逐一構造器是一種爲結構的成員屬性進行初始化的簡便方法。下面的例子定義了一個叫Size的結構,和兩個屬性分別叫width和height。每一個屬性都是Double類型的而且被初始化爲0.0。
由於每一個存儲屬性都有默認值,在Size結構建立一個實例的時候就能夠自動調用這個成員逐一構造器init(width:height:):
struct Size { var width = 0.0, height = 0.0 } let twoByTwo = Size(width: 2.0, height: 2.0)
四、數值類型的構造器代理
在實例的初始化過程當中,構造器能夠調用其餘的構造器來完成初始化。這個過程叫構造器代理,能夠避免多個構造器的重複代碼。
對於數值類型和類來講,構造器代理的工做形式是不同的。數值類型(結構和枚舉)不支持繼承,所以他們的構造器代理相對簡單,由於它們只能使用本身的構造器代理。可是一個類能夠繼承自另一個類,因此類須要確保在初始化的時候將它全部的存儲屬性都設置爲正確的值。
對於數值類型來講,可使用self.init來調用其餘構造器,注意只能在這個數值類型內部調用相應的構造器。
須要注意的是若是你爲數值類型定義了一個構造器,你就不能再使用默認構造器了。這種特性能夠避免當你提供了一個特別複雜的構造器的時候,另一我的誤使用了默認構造器而出錯。
注意:若是你想要同時使用默認構造器和你本身設置的構造器,不要將這兩種構造器寫在一塊兒,而是使用擴展形式。
下面的示例定義了一個結構Rect來表示一個幾何中的矩形。這個Rect結構須要另外兩個結構來組成,包括Size和Point,初始值均爲0.0:
struct Point { var x = 0.0, y = 0.0 }
如今你有三種初始化Rect結構的方式:直接使用爲origin和size屬性初始化的0值,給定一個指定的origin和size,或者使用中心點和大小來初始化。下面的例子包含了這三種初始化方式:
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) } }
五、類的繼承和初始化
自定義初始化方法要先調用本身類默認初始化方法,本身重寫默認初始化方法要先調用父類默認初始化方法
應該要先調用父類的構造器或者自身的默認構造器,以防止先給屬性賦值了而後才調用父類或者自身的默認構造器把之前的賦值覆蓋了
六、經過閉包或者函數來設置一個默認屬性值
若是存儲屬性的默認值須要額外的特殊設置,可使用閉包或者函數來完成。
閉包或者函數會建立一個臨時變量來做爲返回值爲這個屬性賦值。下面是若是使用閉包賦值的一個示意代碼:
class SomeClass { let someProperty: SomeType = { // create a default value for someProperty inside this closure // someValue must be of the same type as SomeType return someValue }() }
須要注意的是在閉包結尾有兩個小括號,告訴Swift這個閉包是須要當即執行的。
注意:若是你使用閉包來初始化一個屬性,在閉包執行的時候,後續的一些屬性尚未被初始化。在閉包中不要訪問任何後面的屬性,一面發生錯誤,也不能使用self屬性,或者其它實例方法。
下面的例子是一個叫Checkerboard的結構,是由遊戲Checkers來的
image這個遊戲是在一個10×10的黑白相間的格子上進行的。來表示這個遊戲盤,使用了一個叫Checkerboard的結構,其中一個屬性叫boardColors,是一個100個Bool類型的數組。true表示這個格子是黑色,false表示是白色。那麼在初始化的時候能夠經過下面的代碼來初始化:
struct Checkerboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...10 { for j in 1...10 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 10) + column] } }
當一個新的Checkerboard實例建立的時候,閉包會執行,而後boardColor的默認值將會被依次計算而且返回,而後做爲結構的一個屬性。經過使用squareIsBlackAtRow工具函數能夠檢測是否被正確設置:
let board = Checkerboard() print(board.squareIsBlackAtRow(0, column: 1)) print(board.squareIsBlackAtRow(9, column: 9))
十五 析構
在一個類的實例被釋放以前,析構函數會被調用。用關鍵字deinit來定義析構函數,相似於初始化函數用init來定義。析構函數只適用於class類型。
一、析構過程原理
Swift 會自動釋放再也不須要的實例以釋放資源。如自動引用計數那一章描述,Swift 經過自動引用計數(ARC)處理實例的內存管理。一般當你的實例被釋放時不須要手動地去清理。可是,當使用本身的資源時,你可能須要進行一些額外的清理。例如,若是建立了一個自定義的類來打開一個文件,並寫入一些數據,你可能須要在類實例被釋放以前關閉該文件。
在類的定義中,每一個類最多隻能有一個析構函數。析構函數不帶任何參數,在寫法上不帶括號:
deinit { // 執行析構過程 }
析構函數是在實例釋放發生前一步被自動調用。不容許主動調用本身的析構函數。子類繼承了父類的析構函數,而且在子類析構函數實現的最後,父類的析構函數被自動調用。即便子類沒有提供本身的析構函數,父類的析構函數也老是被調用。
由於直到實例的析構函數被調用時,實例纔會被釋放,因此析構函數能夠訪問全部請求實例的屬性,而且根據那些屬性能夠修改它的行爲(好比查找一個須要被關閉的文件的名稱)。
二、析構器操做
這裏是一個析構函數操做的例子。這個例子是一個簡單的遊戲,定義了兩種新類型,Bank和Player。Bank結構體管理一個虛擬貨幣的流通,在這個流通中Bank永遠不可能擁有超過 10,000 的硬幣。在這個遊戲中有且只能有一個Bank存在,所以Bank由帶有靜態屬性和靜態方法的結構體實現,從而存儲和管理其當前的狀態。
struct Bank { static var coinsInBank = 10_000 static func vendCoins(var numberOfCoinsToVend: Int) -> Int { numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank) coinsInBank -= numberOfCoinsToVend return numberOfCoinsToVend } static func receiveCoins(coins: Int) { coinsInBank += coins } }
Bank根據它的coinsInBank屬性來跟蹤當前它擁有的硬幣數量。銀行還提供兩個方法——vendCoins和receiveCoins——用來處理硬幣的分發和收集。
vendCoins方法在 bank 分發硬幣以前檢查是否有足夠的硬幣。若是沒有足夠多的硬幣,Bank返回一個比請求時小的數字(若是沒有硬幣留在 bank 中就返回 0)。vendCoins方法聲明numberOfCoinsToVend爲一個變量參數,這樣就能夠在方法體的內部修改數字,而不須要定義一個新的變量。vendCoins方法返回一個整型值,代表了提供的硬幣的實際數目。
receiveCoins方法只是將 bank 的硬幣存儲和接收到的硬幣數目相加,再保存回 bank。
Player類描述了遊戲中的一個玩家。每個 player 在任什麼時候刻都有必定數量的硬幣存儲在他們的錢包中。這經過 player 的coinsInPurse屬性來體現:
class Player { var coinsInPurse: Int init(coins: Int) { coinsInPurse = Bank.vendCoins(coins) } func winCoins(coins: Int) { coinsInPurse += Bank.vendCoins(coins) } deinit { print("quit") Bank.receiveCoins(coinsInPurse) } }
每一個Player實例都由一個指定數目硬幣組成的啓動額度初始化,這些硬幣在 bank 初始化的過程當中獲得。若是沒有足夠的硬幣可用,Player實例可能收到比指定數目少的硬幣。
Player類定義了一個winCoins方法,該方法從銀行獲取必定數量的硬幣,並把它們添加到玩家的錢包。Player類還實現了一個析構函數,這個析構函數在Player實例釋放前一步被調用。這裏析構函數只是將玩家的全部硬幣都返回給銀行:
var playerOne: Player? = Player(coins: 100) print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") print("There are now \(Bank.coinsInBank) coins left in the bank")
一個新的Player實例隨着一個 100 個硬幣(若是有)的請求而被建立。這個Player實例存儲在一個名爲playerOne的可選Player變量中。這裏使用一個可選變量,是由於玩家能夠隨時離開遊戲。設置爲可選使得你能夠跟蹤當前是否有玩家在遊戲中。
由於playerOne是可選的,因此由一個感嘆號(!)來修飾,每當其winCoins方法被調用時,coinsInPurse屬性被訪問並打印出它的默認硬幣數目。
playerOne!.winCoins(2_000) print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") print("The bank now only has \(Bank.coinsInBank) coins left")
這裏,player 已經贏得了 2,000 硬幣。player 的錢包如今有 2,100 硬幣,bank 只剩餘 7,900 硬幣。
playerOne = nil print("The bank now has \(Bank.coinsInBank) coins")
玩家如今已經離開了遊戲。這代表是要將可選的playerOne變量設置爲nil,意思是「沒有Player實例」。當這種狀況發生的時候,playerOne變量對Player實例的引用被破壞了。沒有其它屬性或者變量引用Player實例,所以爲了清空它佔用的內存從而釋放它。在這發生前一步,其析構函數被自動調用,其硬幣被返回到銀行。