類與結構體app
Swift中,並不要求把自定義類或結構的接口和實現寫在不一樣的文件中。你在一個文件中定義類或結構體,那麼這個類或結構體的外部接口就自動能夠在其餘代碼中使用了。ide
類和結構有不少類似和區別,相同點有:函數
》能夠定義屬性來存儲值idea
》能夠定義方法提供各類功能spa
》能夠定義下標使得能夠經過下標語法獲取值指針
》能夠定義初始化方法來設定初始狀態code
》能夠被擴展以提供默認實現以外的功能blog
》遵照協議以提供標準化的功能繼承
此外,類具有一些結構體不具有的特性,如:接口
》繼承容許一個類繼承自另外一個類
》類型投射能夠在運行時檢查而且影響類實例的類型
》析構方法容許實例釋放它被分配的任何資源
》引用計數容許多個引用指向一個類實例 (結構體在Swift中傳遞時老是拷貝傳遞的,不使用引用計數)
語法
類與結構的定義語法相似,用關鍵class(或struct)加類名(或結構體名)後面跟一對大括號。好比:
struct Resolution { var width = 0 var height = 0 } class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String? }
類與結構體的實例
建立類和結構體的實例語法很是相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
獲取屬性
用點語法獲取類或結構體實例的屬性值,好比:
println("The width of someResolution is \(someResolution.width)") // prints "The width of someResolution is 0」 someVideoMode.resolution.width = 1280
注意:和OC不同,Swift容許你給結構體的屬性的子屬性直接賦值,而不要把整個屬性替換爲新值。
結構體的逐成員初始化方法
當定義結構體的時候,它會自動生成一個逐成員的初始化方法用來初始化新實例的成員屬性,好比:
let vga = Resolution(width: 640, height: 480)
類並無這個特性。
結構體和枚舉及Swift中全部基本類型都是值類型,當它們被賦值給常量、變量、或者被傳遞給函數的時候,值都會被拷貝。而類是引用類型的,當被賦值給常量、變量、或者被傳遞給函數的時候,它們並不會拷貝一份,而是直接傳遞指向這個實例的同一引用。
身份運算符
由於類是引用類型,所以,可能會存在多個常量或者變量指向同一個實例。此時會須要確認多個常量或者變量是不是指向的徹底同一個實例,所以,Swift提供了兩個身份操做符:
身份相同 (===)
身份不一樣 (!==)
注意這個身份相同運算符(===)和相等運算符(==)是不同的,它意味着兩個常量或者變量指向的是徹底相同的一個實例。
指針
C/C++/OC中都是使用指針來標識內存中的地址,Swift中指向實例的常量/變量相似於這種指針,但它不是直接指向內存地址的指針,也不須要你在它以前寫星號(*)來標識你正在建立一個引用。
選擇類仍是結構體
類和結構體都是用來定義自定義數據類型的,結構體是值類型,按值傳遞的,類是引用類型的,按引用傳遞的,通常狀況下,如下狀況應該考慮建立結構體:
》建立這個結構的主要目的是包含一些相關聯的簡單數據值
》有理由在將結構賦值給變量/常量的時候須要拷貝它的值而不是直接傳遞引用
》結構存儲的任何屬性都是指望進行值拷貝的它們本身值類型
》結構不須要從已有類型中繼承屬性或者行爲
其餘狀況下,定義一個類,而後建立它的實例。
Swift的String、Array、Dictionary都是用結構體實現的,所以是值類型,這和OC中的NSString、NSArray、NSDictionary是不同的,後者都是用類實現的,所以是引用類型。
注意:值類型在傳遞過程當中是會拷貝值,但並不表明每次都會發生真實的拷貝,只是意義上的拷貝,Swift會決定是否真的須要拷貝它的值。
屬性
屬性將值關聯到特定的類、結構、枚舉。
存儲屬性將常量/變量做爲實例的一部分存儲起來,而計算屬性則是計算(而非存儲)值。計算屬性能夠由類、結構、枚舉提供,而存儲屬性只能由類和結構提供。
存儲屬性和計算屬性通常都是和特定類型的實例相關聯的,可是,屬性其實也能夠和類型自己相關聯,就是所謂的類型屬性。
此外,還能夠自定義觀察者來監聽屬性值的改變,屬性觀察者能夠被添加到你自定義的存儲屬性以及子類從它父類繼承獲得的屬性。
存儲屬性
最簡單的形式,就是做爲類或者結構體的實例的一部分存儲起來的常量/變量。你能夠在定義存儲屬性的時候就提供默認值,而且能夠在初始化的時候設定和改變存儲屬性的值,即便存數屬性是常量的。
以下的例子中,定義了一個結構體,描述一個定長範圍,一旦它的實例被建立,那麼其length屬性就不可更改:
struct FixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) // the range represents integer values 0, 1, and 2 rangeOfThreeItems.firstValue = 6 // the range now represents integer values 6, 7, and 8
在上面的例子中,length屬性是一個常量,在初始化的能夠被設定和更改,在那以後就不能再被更改了。
常量結構體實例的存儲屬性
若是你建立了一個結構體實例並將其賦值給一個常量,那麼它的全部屬性都不能再被更改,即便該屬性是以變量形式定義的。
這主要是由於結構體是值類型的,當一個實例被標記爲常量的時候,它的全部屬性就都被標記爲常量了。而類就不是這樣,它是引用類型,當把類實例賦值給常量時,它的變量屬性仍然是能夠更改的。
懶存儲屬性
懶存儲屬性是指在初始化的時候並不提供值,直到第一次使用的時候才提供。經過在定義以前加上lazy關鍵字來標識一個懶存儲屬性。
注意:你必須老是將懶存儲屬性定義爲變量(var),由於它的值可能在實例初始化結束以後才能得到,而常量屬性的值必須在實例初始化結束以前就能得到。
當屬性的初始值依賴與一個實例初始化結束以後才能肯定值的外部因素時,懶屬性就比較有用。而且,當屬性的初始值須要很複雜或者耗費資源的計算才能得到,所以只有在須要的時候才這麼作,此時懶屬性也頗有用。好比:
class DataImporter { /* DataImporter 從外部文件導入數據. 假設這個類須要耗費很多時間來初始化. */ var fileName = "data.txt" // 此處導入數據 } class DataManager { lazy var importer = DataImporter() var data = [String]() // 此到處理數據 } let manager = DataManager() manager.data.append("Some data") manager.data.append("Some more data") //做爲manager屬性的DataImporter實例並無被建立
一直到須要使用這個屬性的時候,它的初始值纔會被計算出來:
println(manager.importer.fileName) // DataImporter 實例被建立 // prints "data.txt」
存儲屬性和實例變量
熟悉OC的人應該知道有兩種方式將值和引用存儲爲類實例的一部分。除了屬性以外,還能夠用實例變量做爲屬性存儲的值的後備存儲。
Swift將這兩個概念統一到了屬性定義中,一個Swift屬性並無一個對應的實例變量,而且屬性後備存儲是不能直接訪問的。屬性的全部信息--包括名稱,類型,內存管理特性,都是做爲類型定義的一部分在一個地方被定義的。
計算屬性
在存儲屬性以外,類、結構、枚舉均可以定義並不存儲值的計算屬性,他們提供一個getter和一個setter(可選)來間接獲取或者設定其餘的屬性和值。好比:
struct Point { var x = 0.0, y = 0.0 } struct Size { var width = 0.0, height = 0.0 } struct Rect { var origin = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x - (size.width / 2) origin.y = newCenter.y - (size.height / 2) } } } var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0)) let initialSquareCenter = square.center square.center = Point(x: 15.0, y: 15.0) println("square.origin is now at (\(square.origin.x), \(square.origin.y))") // prints "square.origin is now at (10.0, 10.0)」
這裏的center屬性就是計算屬性,由於它的值是能夠經過origin和size計算出來的,所以不須要存儲爲顯示Point型值。這個屬性依然是經過點屬性獲取的,不過此時是觸發了getter的調用以獲取值。設定新值的時候也是相似的過程。
setter的簡寫形式
若是計算屬性的setter並無爲要設定的新值指定名字,那麼默認的名字newValue會被使用,所以上個例子能夠改成:
struct AlternativeRect { var origin = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set { origin.x = newValue.x - (size.width / 2) origin.y = newValue.y - (size.height / 2) } } }
若是計算屬性只有getter而沒有setter的時候,只能獲取它的值而不能設定或者改變它的值,此時成爲只讀計算屬性。
計算屬性(包括只讀計算屬性)只能用var來定義,由於他們的值並非肯定的。let只用來定義那些在初始化以後值明確地不能改變的屬性。
只讀計算屬性的定義能夠簡寫爲省略get關鍵字及其大括號:
struct Cuboid { var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth } }
屬性觀察者
屬性觀察者監聽並響應屬性值的變化,只要屬性的值被新設定,觀察者都會被調用,即便設定的新值等於原來的值。
你能夠給任意存儲屬性添加觀察者,懶存儲屬性除外。
你也能夠經過在子類中重載屬性,給任意繼承過來的屬性(包括計算屬性)添加觀察者。你不須要給非繼承的計算屬性添加觀察者,由於你能夠直接在它的setter裏邊處理這些事情。
你能夠選擇性地給屬性添加如下觀察者:
willSet:在新值被存儲以前觸發,會傳遞進一個常量表明新的值,若是你在willSet實現中沒有指定參數名稱,會使用默認名稱newValue
didSet:新值被存儲以後馬上觸發,會傳遞進一個常量表明新的值,若是你在willSet實現中沒有指定參數名稱,會使用默認名稱oldValue
注意:若是值的改變是在初始化的時候,這兩個監聽都不會被觸發。
class StepCounter { var totalSteps: Int = 0 { willSet(newTotalSteps) { println("About to set totalSteps to \(newTotalSteps)") } didSet { if totalSteps > oldValue { println("Added \(totalSteps - oldValue) steps") } } } }
全局變量和局部變量一樣具備前面介紹的屬性的計算和觀察能力,前面介紹的全部全局變量都是存儲變量,像存儲屬性同樣,它們存儲特定類型的值,並容許被設定和獲取。
也可定義計算變量或者給存儲變量添加觀察者,全局變量或者局部變量均可以。
全局常量和變量都是懶計算的,像懶存儲屬性同樣,不過它們不須要添加lazy關鍵字。局部變量和常量都不是懶計算的。
類型屬性
實例屬性是指那些被特定類型的實例擁有的屬性。每次你建立這個類型的新實例,它就擁有本身的屬性值,和其餘實例是分開的。
你也能夠定義屬於某個類型的屬性,它不屬於任何一個這種類型的實例。不管建立多少實例,這種屬性只有一份。
類型屬性適合定義那些全部實例都通用的屬性。好比全部實例都能用的常量屬性(像C中的靜態常量),或者全部實例都能訪問的變量屬性(像C中的靜態變量)。
對值類型(結構和枚舉),你能夠定義計算式和存儲式類型屬性,對類而言,你只能定義計算式類型屬性。存儲式類型屬性能夠是常量和變量,計算式類型屬性只能是變量,這和實例屬性是同樣的。
注意:和存儲式實例屬性不同,你必須給存儲式類型屬性設定默認值,由於類型自己沒有初始化方法。
在C和OC中,經過定義爲全局靜態(static)變量來定義和類型關聯的靜態常量和變量。Swift中,類型屬性是做爲類型定義的一部分寫在類型外部大括號以內的,而且每一個類型屬性都是顯示地限定了它支持的類型。
經過static關鍵字定義類型屬性,對於類類型的計算式類型屬性,能夠用class關鍵字代替,以便子類能夠重載父類的實現,一下示例展現了存儲式和計算式類型屬性的語法:
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } enum SomeEnumeration { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } } class SomeClass { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here } class var overrideableComputedTypeProperty: Int { // return an Int value here } }
示例中的計算式屬性是用的只讀的簡寫形式,沒有setter,固然這不是必須的。
查詢和設置類型屬性
可實例屬性同樣,類型屬性的查詢和設置經過點屬性,不過它是在類型自己上操做,不是該類型的實例。好比:
println(SomeClass.computedTypeProperty) // prints "42" println(SomeStructure.storedTypeProperty) // prints "Some value." SomeStructure.storedTypeProperty = "Another value." println(SomeStructure.storedTypeProperty) // prints "Another value.」