內(nèi)部網(wǎng)站做域名解析到端口發(fā)布軟文
Swift 類是構(gòu)建代碼所用的一種通用且靈活的構(gòu)造體。
我們可以為類定義屬性(常量、變量)和方法。
與其他編程語言所不同的是,Swift 并不要求你為自定義類去創(chuàng)建獨立的接口和實現(xiàn)文件。你所要做的是在一個單一文件中定義一個類,系統(tǒng)會自動生成面向其它代碼的外部接口。
類和結(jié)構(gòu)體對比
Swift 中類和結(jié)構(gòu)體有很多共同點。共同處在于:
- 定義屬性用于存儲值
- 定義方法用于提供功能
- 定義附屬腳本用于訪問值
- 定義構(gòu)造器用于生成初始化值
- 通過擴(kuò)展以增加默認(rèn)實現(xiàn)的功能
- 符合協(xié)議以對某類提供標(biāo)準(zhǔn)功能
與結(jié)構(gòu)體相比,類還有如下的附加功能:
- 繼承允許一個類繼承另一個類的特征
- 類型轉(zhuǎn)換允許在運(yùn)行時檢查和解釋一個類實例的類型
- 解構(gòu)器允許一個類實例釋放任何其所被分配的資源
- 引用計數(shù)允許對一個類的多次引用
語法:
class classname {Definition 1Definition 2……Definition N
}
類定義
class student{var studname: Stringvar mark: Int var mark2: Int
}
實例化類:
let studrecord = student()
作為引用類型訪問類屬性
類的屬性可以通過?.?來訪問。格式為:實例化類名.屬性名:
恒等運(yùn)算符
因為類是引用類型,有可能有多個常量和變量在后臺同時引用某一個類實例。
為了能夠判定兩個常量或者變量是否引用同一個類實例,Swift 內(nèi)建了兩個恒等運(yùn)算符:
恒等運(yùn)算符 | 不恒等運(yùn)算符 |
---|---|
運(yùn)算符為:=== | 運(yùn)算符為:!== |
如果兩個常量或者變量引用同一個類實例則返回 true | 如果兩個常量或者變量引用不同一個類實例則返回 true |
實例
import Cocoaclass SampleClass: Equatable {let myProperty: Stringinit(s: String) {myProperty = s} } func ==(lhs: SampleClass, rhs: SampleClass) -> Bool {return lhs.myProperty == rhs.myProperty }let spClass1 = SampleClass(s: "Hello") let spClass2 = SampleClass(s: "Hello")if spClass1 === spClass2 {// falseprint("引用相同的類實例 \(spClass1)") }if spClass1 !== spClass2 {// trueprint("引用不相同的類實例 \(spClass2)") }
以上程序執(zhí)行輸出結(jié)果為:
引用不相同的類實例 SampleClass
Swift 屬性
Swift 屬性將值跟特定的類、結(jié)構(gòu)或枚舉關(guān)聯(lián)。
屬性可分為存儲屬性和計算屬性:
存儲屬性 | 計算屬性 |
---|---|
存儲常量或變量作為實例的一部分 | 計算(而不是存儲)一個值? |
用于類和結(jié)構(gòu)體 | 用于類、結(jié)構(gòu)體和枚舉 |
存儲屬性和計算屬性通常用于特定類型的實例。
屬性也可以直接用于類型本身,這種屬性稱為類型屬性。
另外,還可以定義屬性觀察器來監(jiān)控屬性值的變化,以此來觸發(fā)一個自定義的操作。屬性觀察器可以添加到自己寫的存儲屬性上,也可以添加到從父類繼承的屬性上。
存儲屬性
簡單來說,一個存儲屬性就是存儲在特定類或結(jié)構(gòu)體的實例里的一個常量或變量。
存儲屬性可以是變量存儲屬性(用關(guān)鍵字var定義),也可以是常量存儲屬性(用關(guān)鍵字let定義)。
-
可以在定義存儲屬性的時候指定默認(rèn)值
-
也可以在構(gòu)造過程中設(shè)置或修改存儲屬性的值,甚至修改常量存儲屬性的值
延遲存儲屬性
延遲存儲屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時候才會計算其初始值的屬性。
在屬性聲明前使用?lazy?來標(biāo)示一個延遲存儲屬性。
注意:
必須將延遲存儲屬性聲明成變量(使用var
關(guān)鍵字),因為屬性的值在實例構(gòu)造完成之前可能無法得到。而常量屬性在構(gòu)造過程完成之前必須要有初始值,因此無法聲明成延遲屬性。?
延遲存儲屬性一般用于:
-
延遲對象的創(chuàng)建。
-
當(dāng)屬性的值依賴于其他未知類
import Cocoaclass sample {lazy var no = number() // `var` 關(guān)鍵字是必須的 }class number {var name = "Runoob Swift 教程" }var firstsample = sample() print(firstsample.no.name)
以上程序執(zhí)行輸出結(jié)果為:
Runoob Swift 教程
實例化變量
如果您有過 Objective-C 經(jīng)驗,應(yīng)該知道Objective-C 為類實例存儲值和引用提供兩種方法。對于屬性來說,也可以使用實例變量作為屬性值的后端存儲。
Swift 編程語言中把這些理論統(tǒng)一用屬性來實現(xiàn)。Swift 中的屬性沒有對應(yīng)的實例變量,屬性的后端存儲也無法直接訪問。這就避免了不同場景下訪問方式的困擾,同時也將屬性的定義簡化成一個語句。
一個類型中屬性的全部信息——包括命名、類型和內(nèi)存管理特征——都在唯一一個地方(類型定義中)定義。
計算屬性
除存儲屬性外,類、結(jié)構(gòu)體和枚舉可以定義計算屬性,計算屬性不直接存儲值,而是提供一個 getter 來獲取值,一個可選的 setter 來間接設(shè)置其他屬性或變量的值。
import Cocoaclass sample {var no1 = 0.0, no2 = 0.0var length = 300.0, breadth = 150.0var middle: (Double, Double) {get{return (length / 2, breadth / 2)}set(axis){no1 = axis.0 - (length / 2)no2 = axis.1 - (breadth / 2)}} }var result = sample() print(result.middle) result.middle = (0.0, 10.0)print(result.no1) print(result.no2)
以上程序執(zhí)行輸出結(jié)果為:
(150.0, 75.0) -150.0 -65.0
如果計算屬性的 setter 沒有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱 newValue。
只讀計算屬性
只有 getter 沒有 setter 的計算屬性就是只讀計算屬性。
只讀計算屬性總是返回一個值,可以通過點(.)運(yùn)算符訪問,但不能設(shè)置新的值。
import Cocoaclass film {var head = ""var duration = 0.0var metaInfo: [String:String] {return ["head": self.head,"duration":"\(self.duration)"]} }var movie = film() movie.head = "Swift 屬性" movie.duration = 3.09print(movie.metaInfo["head"]!) print(movie.metaInfo["duration"]!)
以上程序執(zhí)行輸出結(jié)果為:
Swift 屬性 3.09
注意:
必須使用
var
關(guān)鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。let
關(guān)鍵字只用來聲明常量屬性,表示初始化后再也無法修改的值。
屬性觀察器
屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時候都會調(diào)用屬性觀察器,甚至新的值和現(xiàn)在的值相同的時候也不例外。
可以為除了延遲存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重載屬性的方式為繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器。
注意:
不需要為無法重載的計算屬性添加屬性觀察器,因為可以通過 setter 直接監(jiān)控和響應(yīng)值的變化。?
可以為屬性添加如下的一個或全部觀察器:
willSet
在設(shè)置新的值之前調(diào)用didSet
在新的值被設(shè)置之后立即調(diào)用- willSet和didSet觀察器在屬性初始化過程中不會被調(diào)用
import Cocoaclass Samplepgm {var counter: Int = 0{willSet(newTotal){print("計數(shù)器: \(newTotal)")}didSet{if counter > oldValue {print("新增數(shù) \(counter - oldValue)")}}} } let NewCounter = Samplepgm() NewCounter.counter = 100 NewCounter.counter = 800
以上程序執(zhí)行輸出結(jié)果為:
計數(shù)器: 100 新增數(shù) 100 計數(shù)器: 800 新增數(shù) 700
全局變量和局部變量
計算屬性和屬性觀察器所描述的模式也可以用于全局變量和局部變量。
局部變量 | 全局變量 |
---|---|
在函數(shù)、方法或閉包內(nèi)部定義的變量。 | 函數(shù)、方法、閉包或任何類型之外定義的變量。 |
用于存儲和檢索值。 | 用于存儲和檢索值。 |
存儲屬性用于獲取和設(shè)置值。 | 存儲屬性用于獲取和設(shè)置值。 |
也用于計算屬性。 | 也用于計算屬性。 |
類型屬性
類型屬性是作為類型定義的一部分寫在類型最外層的花括號({})內(nèi)。
使用關(guān)鍵字 static 來定義值類型的類型屬性,關(guān)鍵字 class 來為類定義類型屬性。
獲取和設(shè)置類型屬性的值
類似于實例的屬性,類型屬性的訪問也是通過點運(yùn)算符(.)來進(jìn)行。但是,類型屬性是通過類型本身來獲取和設(shè)置,而不是通過實例。實例如下:
Swift 方法
Swift 方法是與某些特定類型相關(guān)聯(lián)的函數(shù)
在 Objective-C 中,類是唯一能定義方法的類型。但在 Swift 中,你不僅能選擇是否要定義一個類/結(jié)構(gòu)體/枚舉,還能靈活的在你創(chuàng)建的類型(類/結(jié)構(gòu)體/枚舉)上定義方法。
實例方法
在 Swift 語言中,實例方法是屬于某個特定類、結(jié)構(gòu)體或者枚舉類型實例的方法。
實例方法提供以下方法:
-
可以訪問和修改實例屬性
-
提供與實例目的相關(guān)的功能
實例方法要寫在它所屬的類型的前后大括號({})之間。
實例方法能夠隱式訪問它所屬類型的所有的其他實例方法和屬性。
實例方法只能被它所屬的類的某個特定實例調(diào)用。
實例方法不能脫離于現(xiàn)存的實例而被調(diào)用。
語法
func funcname(Parameters) -> returntype
{Statement1Statement2……Statement Nreturn parameters
}
方法的局部參數(shù)名稱和外部參數(shù)名稱
Swift 函數(shù)參數(shù)可以同時有一個局部名稱(在函數(shù)體內(nèi)部使用)和一個外部名稱(在調(diào)用函數(shù)時使用
Swift 中的方法和 Objective-C 中的方法極其相似。像在 Objective-C 中一樣,Swift 中方法的名稱通常用一個介詞指向方法的第一個參數(shù),比如:with,for,by等等。
Swift 默認(rèn)僅給方法的第一個參數(shù)名稱一個局部參數(shù)名稱;默認(rèn)同時給第二個和后續(xù)的參數(shù)名稱為全局參數(shù)名稱。
以下實例中 'no1' 在swift中聲明為局部參數(shù)名稱。'no2' 用于全局的聲明并通過外部程序訪問。
是否提供外部名稱設(shè)置
我們強(qiáng)制在第一個參數(shù)添加外部名稱把這個局部名稱當(dāng)作外部名稱使用(Swift 2.0前是使用 # 號)。
相反,我們呢也可以使用下劃線(_)設(shè)置第二個及后續(xù)的參數(shù)不提供一個外部名稱。
self 屬性
類型的每一個實例都有一個隱含屬性叫做self,self 完全等同于該實例本身。
你可以在一個實例的實例方法中使用這個隱含的self屬性來引用當(dāng)前實例。
在實例方法中修改值類型
Swift 語言中結(jié)構(gòu)體和枚舉是值類型。一般情況下,值類型的屬性不能在它的實例方法中被修改。
但是,如果你確實需要在某個具體的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以選擇變異(mutating)這個方法,然后方法就可以從方法內(nèi)部改變它的屬性;并且它做的任何改變在方法結(jié)束時還會保留在原始結(jié)構(gòu)中。
方法還可以給它隱含的self屬性賦值一個全新的實例,這個新實例在方法結(jié)束后將替換原來的實例。
import Cocoastruct area {var length = 1var breadth = 1func area() -> Int {return length * breadth} mutating func scaleBy(res: Int) {length *= resbreadth *= resprint(length)print(breadth)} }var val = area(length: 3, breadth: 5) val.scaleBy(res: 3) val.scaleBy(res: 30) val.scaleBy(res: 300)
以上程序執(zhí)行輸出結(jié)果為:
9
15
270
450
81000
135000
在可變方法中給 self 賦值
可變方法能夠賦給隱含屬性 self 一個全新的實例。
mutating func scaleBy(res: Int) {self.length *= resself.breadth *= resprint(length)print(breadth)}
類型方法?
實例方法是被類型的某個實例調(diào)用的方法,你也可以定義類型本身調(diào)用的方法,這種方法就叫做類型方法。
聲明結(jié)構(gòu)體和枚舉的類型方法,在方法的func關(guān)鍵字之前加上關(guān)鍵字static。類可能會用關(guān)鍵字class來允許子類重寫父類的實現(xiàn)方法。
類型方法和實例方法一樣用點號(.)語法調(diào)用。
import Cocoaclass Math
{class func abs(number: Int) -> Int{if number < 0{return (-number)}else{return number}}
}struct absno
{static func abs(number: Int) -> Int{if number < 0{return (-number)}else{return number}}
}let no = Math.abs(number: -35)
let num = absno.abs(number: -5)print(no)
print(num)
以上程序執(zhí)行輸出結(jié)果為:
35
5
Swift 下標(biāo)腳本
下標(biāo)腳本 可以定義在類(Class)、結(jié)構(gòu)體(structure)和枚舉(enumeration)這些目標(biāo)中,可以認(rèn)為是訪問對象、集合或序列的快捷方式,不需要再調(diào)用實例的特定的賦值和訪問方法。
舉例來說,用下標(biāo)腳本訪問一個數(shù)組(Array)實例中的元素可以這樣寫 someArray[index] ,訪問字典(Dictionary)實例中的元素可以這樣寫 someDictionary[key]。
對于同一個目標(biāo)可以定義多個下標(biāo)腳本,通過索引值類型的不同來進(jìn)行重載,而且索引值的個數(shù)可以是多個。
下標(biāo)腳本語法及應(yīng)用
語法
下標(biāo)腳本允許你通過在實例后面的方括號中傳入一個或者多個的索引值來對實例進(jìn)行訪問和賦值。
語法類似于實例方法和計算型屬性的混合。
與定義實例方法類似,定義下標(biāo)腳本使用subscript關(guān)鍵字,顯式聲明入?yún)?#xff08;一個或多個)和返回類型。
與實例方法不同的是下標(biāo)腳本可以設(shè)定為讀寫或只讀。這種方式又有點像計算型屬性的getter和setter:
subscript(index: Int) -> Int {get {// 用于下標(biāo)腳本值的聲明}set(newValue) {// 執(zhí)行賦值操作}
}
實例 1
import Cocoastruct subexample {let decrementer: Intsubscript(index: Int) -> Int {return decrementer / index}
}
let division = subexample(decrementer: 100)print("100 除以 9 等于 \(division[9])")
print("100 除以 2 等于 \(division[2])")
print("100 除以 3 等于 \(division[3])")
print("100 除以 5 等于 \(division[5])")
print("100 除以 7 等于 \(division[7])")
以上程序執(zhí)行輸出結(jié)果為:
100 除以 9 等于 11
100 除以 2 等于 50
100 除以 3 等于 33
100 除以 5 等于 20
100 除以 7 等于 14
在上例中,通過 subexample 結(jié)構(gòu)體創(chuàng)建了一個除法運(yùn)算的實例。數(shù)值 100 作為結(jié)構(gòu)體構(gòu)造函數(shù)傳入?yún)?shù)初始化實例成員 decrementer。
你可以通過下標(biāo)腳本來得到結(jié)果,比如 division[2] 即為 100 除以 2。
下標(biāo)腳本選項
下標(biāo)腳本允許任意數(shù)量的入?yún)⑺饕?#xff0c;并且每個入?yún)㈩愋鸵矝]有限制。
下標(biāo)腳本的返回值也可以是任何類型。
下標(biāo)腳本可以使用變量參數(shù)和可變參數(shù)。
一個類或結(jié)構(gòu)體可以根據(jù)自身需要提供多個下標(biāo)腳本實現(xiàn),在定義下標(biāo)腳本時通過傳入?yún)?shù)的類型進(jìn)行區(qū)分,使用下標(biāo)腳本時會自動匹配合適的下標(biāo)腳本實現(xiàn)運(yùn)行,這就是下標(biāo)腳本的重載。
Swift 繼承
繼承我們可以理解為一個類獲取了另外一個類的方法和屬性。
當(dāng)一個類繼承其它類時,繼承類叫子類,被繼承類叫超類(或父類)
在 Swift 中,類可以調(diào)用和訪問超類的方法,屬性和下標(biāo)腳本,并且可以重寫它們。
我們也可以為類中繼承來的屬性添加屬性觀察器。
基類
沒有繼承其它類的類,稱之為基類(Base Class)。
子類
子類指的是在一個已有類的基礎(chǔ)上創(chuàng)建一個新的類。
為了指明某個類的超類,將超類名寫在子類名的后面,用冒號(:)分隔,語法格式如下
class SomeClass: SomeSuperclass {// 類的定義 }
實例
以下實例中我們定義了超類 StudDetails,然后使用子類 Tom 繼承它:
class StudDetails
{var mark1: Int;var mark2: Int;init(stm1:Int, results stm2:Int){mark1 = stm1;mark2 = stm2;}func show(){print("Mark1:\(self.mark1), Mark2:\(self.mark2)")}
}class Tom : StudDetails
{init(){super.init(stm1: 93, results: 89)}
}let tom = Tom()
tom.show()
以上程序執(zhí)行輸出結(jié)果為:
Mark1:93, Mark2:89
重寫(Overriding)
子類可以通過繼承來的實例方法,類方法,實例屬性,或下標(biāo)腳本來實現(xiàn)自己的定制功能,我們把這種行為叫重寫(overriding)。
我們可以使用 override 關(guān)鍵字來實現(xiàn)重寫。
訪問超類的方法、屬性及下標(biāo)腳本
你可以通過使用super前綴來訪問超類的方法,屬性或下標(biāo)腳本。
重寫 | 訪問方法,屬性,下標(biāo)腳本 |
---|---|
方法 | super.somemethod() |
屬性 | super.someProperty() |
下標(biāo)腳本 | super[someIndex] |
重寫方法和屬性
重寫方法
在我們的子類中我們可以使用 override 關(guān)鍵字來重寫超類的方法。
以下實例中我們重寫了 show() 方法:
class SuperClass {
func show() {print("這是超類 SuperClass")}
}class SubClass: SuperClass {override func show() {print("這是子類 SubClass")}
}let superClass = SuperClass()
superClass.show()let subClass = SubClass()
subClass.show()
以上程序執(zhí)行輸出結(jié)果為:
這是超類 SuperClass
這是子類 SubClass
重寫屬性
你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是存儲型的還是計算型的屬性。
子類并不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型。所以你在重寫一個屬性時,必需將它的名字和類型都寫出來。
注意點:
-
如果你在重寫屬性中提供了 setter,那么你也一定要提供 getter。
-
如果你不想在重寫版本中的 getter 里修改繼承來的屬性值,你可以直接通過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字。
以下實例我們定義了超類 Circle 及子類 Rectangle, 在 Rectangle 類中我們重寫屬性 area:
lass Circle {var radius = 12.5var area: String {return "矩形半徑 \(radius) "} }// 繼承超類 Circle class Rectangle: Circle {var print = 7 override var area: String {return super.area + " ,但現(xiàn)在被重寫為 \(print)"} }let rect = Rectangle() rect.radius = 25.0 rect.print = 3 print("Radius \(rect.area)")
以上程序執(zhí)行輸出結(jié)果為:
Radius 矩形半徑 25.0 ,但現(xiàn)在被重寫為 3
重寫屬性觀察器
你可以在屬性重寫中為一個繼承來的屬性添加屬性觀察器。這樣一來,當(dāng)繼承來的屬性值發(fā)生改變時,你就會監(jiān)測到。
注意:你不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。
class Square: Rectangle {override var radius: Double {didSet {print = Int(radius/5.0)+1}}
}
防止重寫
我們可以使用 final 關(guān)鍵字防止它們被重寫。
如果你重寫了final方法,屬性或下標(biāo)腳本,在編譯時會報錯。
你可以通過在關(guān)鍵字class前添加final特性(final class)來將整個類標(biāo)記為 final 的,這樣的類是不可被繼承的,否則會報編譯錯誤。
final class Circle {final var radius = 12.5var area: String {return "矩形半徑為 \(radius) "}
}
class Rectangle: Circle {var print = 7override var area: String {return super.area + " ,但現(xiàn)在被重寫為 \(print)"}
}
由于以上實例使用了 final 關(guān)鍵字不允許重寫,所以執(zhí)行會報錯:
error: var overrides a 'final' varoverride var area: String {^
note: overridden declaration is herevar area: String {^
error: var overrides a 'final' varoverride var radius: Double {^
note: overridden declaration is herefinal var radius = 12.5^
error: inheritance from a final class 'Circle'
class Rectangle: Circle {^
Swift 構(gòu)造過程
構(gòu)造過程是為了使用某個類、結(jié)構(gòu)體或枚舉類型的實例而進(jìn)行的準(zhǔn)備過程。這個過程包含了為實例中的每個屬性設(shè)置初始值和為其執(zhí)行必要的準(zhǔn)備和初始化任務(wù)。
Swift 構(gòu)造函數(shù)使用 init() 方法。
與 Objective-C 中的構(gòu)造器不同,Swift 的構(gòu)造器無需返回值,它們的主要任務(wù)是保證新實例在第一次使用前完成正確的初始化。
類實例也可以通過定義析構(gòu)器(deinitializer)在類實例釋放之前執(zhí)行清理內(nèi)存的工作。
存儲型屬性的初始賦值
類和結(jié)構(gòu)體在實例創(chuàng)建時,必須為所有存儲型屬性設(shè)置合適的初始值。
存儲屬性在構(gòu)造器中賦值時,它們的值是被直接設(shè)置的,不會觸發(fā)任何屬性觀測器。
存儲屬性在構(gòu)造器中賦值流程:
-
創(chuàng)建初始值。
-
在屬性定義中指定默認(rèn)屬性值。
-
初始化實例,并調(diào)用 init() 方法。
構(gòu)造器
構(gòu)造器在創(chuàng)建某特定類型的新實例時調(diào)用。它的最簡形式類似于一個不帶任何參數(shù)的實例方法,以關(guān)鍵字init命名。
語法
init()
{// 實例化后執(zhí)行的代碼
}
實例
以下結(jié)構(gòu)體定義了一個不帶參數(shù)的構(gòu)造器 init,并在里面將存儲型屬性 length 和 breadth 的值初始化為 6 和 12:
struct rectangle {var length: Doublevar breadth: Doubleinit() {length = 6breadth = 12}
}
var area = rectangle()
print("矩形面積為 \(area.length*area.breadth)")
以上程序執(zhí)行輸出結(jié)果為:
矩形面積為 72.0
默認(rèn)屬性值
我們可以在構(gòu)造器中為存儲型屬性設(shè)置初始值;同樣,也可以在屬性聲明時為其設(shè)置默認(rèn)值。
使用默認(rèn)值能讓你的構(gòu)造器更簡潔、更清晰,且能通過默認(rèn)值自動推導(dǎo)出屬性的類型。
以下實例我們在屬性聲明時為其設(shè)置默認(rèn)值:
struct rectangle {// 設(shè)置默認(rèn)值var length = 6var breadth = 12 } var area = rectangle() print("矩形的面積為 \(area.length*area.breadth)")
以上程序執(zhí)行輸出結(jié)果為:
矩形面積為 72
構(gòu)造參數(shù)
你可以在定義構(gòu)造器 init() 時提供構(gòu)造參數(shù),如下所示:
struct Rectangle {var length: Doublevar breadth: Doublevar area: Doubleinit(fromLength length: Double, fromBreadth breadth: Double) {self.length = lengthself.breadth = breadtharea = length * breadth}init(fromLeng leng: Double, fromBread bread: Double) {self.length = lengself.breadth = breadarea = leng * bread}
}let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面積為: \(ar.area)")let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面積為: \(are.area)")
以上程序執(zhí)行輸出結(jié)果為:
面積為: 72.0
面積為: 432.0
內(nèi)部和外部參數(shù)名
跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也存在一個在構(gòu)造器內(nèi)部使用的參數(shù)名字和一個在調(diào)用構(gòu)造器時使用的外部參數(shù)名字。
然而,構(gòu)造器并不像函數(shù)和方法那樣在括號前有一個可辨別的名字。所以在調(diào)用構(gòu)造器時,主要通過構(gòu)造器中的參數(shù)名和類型來確定需要調(diào)用的構(gòu)造器。
如果你在定義構(gòu)造器時沒有提供參數(shù)的外部名字,Swift 會為每個構(gòu)造器的參數(shù)自動生成一個跟內(nèi)部名字相同的外部名。
struct Color {let red, green, blue: Doubleinit(red: Double, green: Double, blue: Double) {self.red = redself.green = greenself.blue = blue}init(white: Double) {red = whitegreen = whiteblue = white} }
沒有外部名稱參數(shù)
如果你不希望為構(gòu)造器的某個參數(shù)提供外部名字,你可以使用下劃線_
來顯示描述它的外部名。
struct Rectangle {var length: Doubleinit(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}//不提供外部名字init(_ area: Double) {length = area}
/ 調(diào)用不提供外部名字
let rectarea = Rectangle(180.0)
print("面積為: \(rectarea.length)")
可選屬性類型
如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性,你都需要將它定義為可選類型optional type(可選屬性類型)。
當(dāng)存儲屬性聲明為可選時,將自動初始化為空 nil。
struct Rectangle {var length: Double?init(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}init(_ area: Double) {length = area}
}
構(gòu)造過程中修改常量屬性
只要在構(gòu)造過程結(jié)束前常量的值能確定,你可以在構(gòu)造過程中的任意時間點修改常量屬性的值。
對某個類實例來說,它的常量屬性只能在定義它的類的構(gòu)造過程中修改;不能在子類中修改。
盡管 length 屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽谄漕惖臉?gòu)造器中設(shè)置它的值:
struct Rectangle { let length: Double?init(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}init(_ area: Double) {length = area} }let rectarea = Rectangle(180.0) print("面積為:\(rectarea.length)")
以上程序執(zhí)行輸出結(jié)果為:
面積為:Optional(180.0)
默認(rèn)構(gòu)造器
默認(rèn)構(gòu)造器將簡單的創(chuàng)建一個所有屬性值都設(shè)置為默認(rèn)值的實例:
以下實例中,ShoppingListItem類中的所有屬性都有默認(rèn)值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設(shè)置默認(rèn)值的默認(rèn)構(gòu)造器
class ShoppingListItem {var name: String?var quantity = 1var purchased = false
}
var item = ShoppingListItem()print("名字為: \(item.name)")
print("數(shù)理為: \(item.quantity)")
print("是否付款: \(item.purchased)")
以上程序執(zhí)行輸出結(jié)果為:
名字為: nil
數(shù)理為: 1
是否付款: false
結(jié)構(gòu)體的逐一成員構(gòu)造器
如果結(jié)構(gòu)體對所有存儲型屬性提供了默認(rèn)值且自身沒有提供定制的構(gòu)造器,它們能自動獲得一個逐一成員構(gòu)造器。
我們在調(diào)用逐一成員構(gòu)造器時,通過與成員屬性名相同的參數(shù)名進(jìn)行傳值來完成對成員屬性的初始賦值。
下面例子中定義了一個結(jié)構(gòu)體 Rectangle,它包含兩個屬性 length 和 breadth。Swift 可以根據(jù)這兩個屬性的初始賦值100.0 、200.0自動推導(dǎo)出它們的類型Double。
struct Rectangle {var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)print("矩形的面積: \(area.length)")
print("矩形的面積: \(area.breadth)")
由于這兩個存儲型屬性都有默認(rèn)值,結(jié)構(gòu)體 Rectangle 自動獲得了一個逐一成員構(gòu)造器 init(width:height:)。 你可以用它來為 Rectangle 創(chuàng)建新的實例。
以上程序執(zhí)行輸出結(jié)果為:
矩形的面積: 24.0
矩形的面積: 32.0
值類型的構(gòu)造器代理
構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理,它能減少多個構(gòu)造器間的代碼重復(fù)。
以下實例中,Rect 結(jié)構(gòu)體調(diào)用了 Size 和 Point 的構(gòu)造過程:
struct Size {var width = 0.0, height = 0.0 } struct Point {var x = 0.0, y = 0.0 }struct Rect {var origin = Point()var size = Size()init() {}init(origin: Point, size: Size) {self.origin = originself.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)} }// origin和size屬性都使用定義時的默認(rèn)值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0): let basicRect = Rect() print("Size 結(jié)構(gòu)體初始值: \(basicRect.size.width, basicRect.size.height) ") print("Rect 結(jié)構(gòu)體初始值: \(basicRect.origin.x, basicRect.origin.y) ")// 將origin和size的參數(shù)值賦給對應(yīng)的存儲型屬性 let originRect = Rect(origin: Point(x: 2.0, y: 2.0),size: Size(width: 5.0, height: 5.0))print("Size 結(jié)構(gòu)體初始值: \(originRect.size.width, originRect.size.height) ") print("Rect 結(jié)構(gòu)體初始值: \(originRect.origin.x, originRect.origin.y) ")//先通過center和size的值計算出origin的坐標(biāo)。 //然后再調(diào)用(或代理給)init(origin:size:)構(gòu)造器來將新的origin和size值賦值到對應(yīng)的屬性中 let centerRect = Rect(center: Point(x: 4.0, y: 4.0),size: Size(width: 3.0, height: 3.0))print("Size 結(jié)構(gòu)體初始值: \(centerRect.size.width, centerRect.size.height) ") print("Rect 結(jié)構(gòu)體初始值: \(centerRect.origin.x, centerRect.origin.y) ")
以上程序執(zhí)行輸出結(jié)果為:
Size 結(jié)構(gòu)體初始值: (0.0, 0.0) Rect 結(jié)構(gòu)體初始值: (0.0, 0.0) Size 結(jié)構(gòu)體初始值: (5.0, 5.0) Rect 結(jié)構(gòu)體初始值: (2.0, 2.0) Size 結(jié)構(gòu)體初始值: (3.0, 3.0) Rect 結(jié)構(gòu)體初始值: (2.5, 2.5)
類的繼承和構(gòu)造過程
Swift 提供了兩種類型的類構(gòu)造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。
指定構(gòu)造器 | 便利構(gòu)造器 |
類中最主要的構(gòu)造器 | 類中比較次要的、輔助型的構(gòu)造器 |
初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類的構(gòu)造器來實現(xiàn)父類的初始化。 | 可以定義便利構(gòu)造器來調(diào)用同一個類中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。你也可以定義便利構(gòu)造器來創(chuàng)建一個特殊用途或特定輸入的實例。 |
每一個類都必須擁有至少一個指定構(gòu)造器 | 只在必要的時候為類提供便利構(gòu)造器 |
Init(parameters) {statements } | convenience init(parameters) {statements
} |
指定構(gòu)造器實例
class mainClass {
var no1 : Int // 局部存儲變量init(no1 : Int) {self.no1 = no1 // 初始化}
}
class subClass : mainClass {var no2 : Int // 新的子類存儲變量init(no1 : Int, no2 : Int) {self.no2 = no2 // 初始化super.init(no1:no1) // 初始化超類}
}let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)print("res 為: \(res.no1)")
print("res2 為: \(res2.no1)")
print("res2 為: \(res2.no2)")
以上程序執(zhí)行輸出結(jié)果為:
res 為: 10
res 為: 10
res 為: 20
便利構(gòu)造器實例
class mainClass {var no1 : Int // 局部存儲變量init(no1 : Int) {self.no1 = no1 // 初始化}
}class subClass : mainClass {var no2 : Intinit(no1 : Int, no2 : Int) {self.no2 = no2super.init(no1:no1)}// 便利方法只需要一個參數(shù)override convenience init(no1: Int) {self.init(no1:no1, no2:0)}
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)print("res 為: \(res.no1)")
print("res2 為: \(res2.no1)")
print("res2 為: \(res2.no2)")
以上程序執(zhí)行輸出結(jié)果為:
res 為: 20
res2 為: 30
res2 為: 50
構(gòu)造器的繼承和重載
Swift 中的子類不會默認(rèn)繼承父類的構(gòu)造器。
父類的構(gòu)造器僅在確定和安全的情況下被繼承。
當(dāng)你重寫一個父類指定構(gòu)造器時,你需要寫override修飾符。
class SuperClass {var corners = 4var description: String {return "\(corners) 邊"}
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")class SubClass: SuperClass {override init() { //重載構(gòu)造器super.init()corners = 5}
}let subClass = SubClass()
print("五角型: \(subClass.description)")
以上程序執(zhí)行輸出結(jié)果為:
矩形: 4 邊
五角型: 5 邊
指定構(gòu)造器和便利構(gòu)造器實例
接下來的例子將在操作中展示指定構(gòu)造器、便利構(gòu)造器和自動構(gòu)造器的繼承。
它定義了包含兩個個類MainClass、SubClass的類層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。
class MainClass {var name: Stringinit(name: String) {self.name = name}convenience init() {self.init(name: "[匿名]")}
}
let main = MainClass(name: "Runoob")
print("MainClass 名字為: \(main.name)")let main2 = MainClass()
print("沒有對應(yīng)名字: \(main2.name)")class SubClass: MainClass {var count: Intinit(name: String, count: Int) {self.count = countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)}
}let sub = SubClass(name: "Runoob")
print("MainClass 名字為: \(sub.name)")let sub2 = SubClass(name: "Runoob", count: 3)
print("count 變量: \(sub2.count)")
以上程序執(zhí)行輸出結(jié)果為:
MainClass 名字為: Runoob
沒有對應(yīng)名字: [匿名]
MainClass 名字為: Runoob
count 變量: 3
類的可失敗構(gòu)造器
如果一個類,結(jié)構(gòu)體或枚舉類型的對象,在構(gòu)造自身的過程中有可能失敗,則為其定義一個可失敗構(gòu)造器。
變量初始化失敗可能的原因有:
-
傳入無效的參數(shù)值。
-
缺少某種所需的外部資源。
-
沒有滿足特定條件。
為了妥善處理這種構(gòu)造過程中可能會失敗的情況。
你可以在一個類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個或多個可失敗構(gòu)造器。其語法為在init關(guān)鍵字后面加添問號(init?)。
枚舉類型的可失敗構(gòu)造器
你可以通過構(gòu)造一個帶一個或多個參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。
實例
下例中,定義了一個名為TemperatureUnit的枚舉類型。其中包含了三個可能的枚舉成員(Kelvin,Celsius,和 Fahrenheit)和一個被用來找到Character值所對應(yīng)的枚舉成員的可失敗構(gòu)造器:
enum TemperatureUnit {// 開爾文,攝氏,華氏case Kelvin, Celsius, Fahrenheit init?(symbol: Character) {switch symbol {case "K":self = .Kelvincase "C":self = .Celsiuscase "F":self = .Fahrenheitdefault:return nil}} }let fahrenheitUnit = TemperatureUnit(symbol: "F") if fahrenheitUnit != nil {print("這是一個已定義的溫度單位,所以初始化成功。") }let unknownUnit = TemperatureUnit(symbol: "X") if unknownUnit == nil {print("這不是一個已定義的溫度單位,所以初始化失敗。") }
以上程序執(zhí)行輸出結(jié)果為:
這是一個已定義的溫度單位,所以初始化成功。
這不是一個已定義的溫度單位,所以初始化失敗。
類的可失敗構(gòu)造器
值類型(如結(jié)構(gòu)體或枚舉類型)的可失敗構(gòu)造器,對何時何地觸發(fā)構(gòu)造失敗這個行為沒有任何的限制。
但是,類的可失敗構(gòu)造器只能在所有的類屬性被初始化后和所有類之間的構(gòu)造器之間的代理調(diào)用發(fā)生完后觸發(fā)失敗行為。
實例
下例子中,定義了一個名為 StudRecord 的類,因為 studname 屬性是一個常量,所以一旦 StudRecord 類構(gòu)造成功,studname 屬性肯定有一個非nil的值。
class StudRecord {let studname: String!init?(studname: String) {self.studname = studnameif studname.isEmpty { return nil }} } if let stname = StudRecord(studname: "失敗構(gòu)造器") {print("模塊為 \(stname.studname)") }
以上程序執(zhí)行輸出結(jié)果為:
模塊為 失敗構(gòu)造器
覆蓋一個可失敗構(gòu)造器
就如同其它構(gòu)造器一樣,你也可以用子類的可失敗構(gòu)造器覆蓋基類的可失敗構(gòu)造器。
者你也可以用子類的非可失敗構(gòu)造器覆蓋一個基類的可失敗構(gòu)造器。
你可以用一個非可失敗構(gòu)造器覆蓋一個可失敗構(gòu)造器,但反過來卻行不通。
一個非可失敗的構(gòu)造器永遠(yuǎn)也不能代理調(diào)用一個可失敗構(gòu)造器。
實例
以下實例描述了可失敗與非可失敗構(gòu)造器:
class Planet {var name: Stringinit(name: String) {self.name = name}convenience init() {self.init(name: "[No Planets]")} } let plName = Planet(name: "Mercury") print("行星的名字是: \(plName.name)")let noplName = Planet() print("沒有這個名字的行星: \(noplName.name)")class planets: Planet {var count: Intinit(name: String, count: Int) {self.count = countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)} }
以上程序執(zhí)行輸出結(jié)果為:
行星的名字是: Mercury 沒有這個名字的行星: [No Planets]
可失敗構(gòu)造器 init!
通常來說我們通過在init關(guān)鍵字后添加問號的方式(init?)來定義一個可失敗構(gòu)造器,但你也可以使用通過在init后面添加驚嘆號的方式來定義一個可失敗構(gòu)造器(init!)。實例如下:
struct StudRecord {let stname: Stringinit!(stname: String) {if stname.isEmpty {return nil }self.stname = stname} }let stmark = StudRecord(stname: "Runoob") if let name = stmark {print("指定了學(xué)生名") }let blankname = StudRecord(stname: "") if blankname == nil {print("學(xué)生名為空") }
以上程序執(zhí)行輸出結(jié)果為:
指定了學(xué)生名 學(xué)生名為空
Swift 析構(gòu)過程
在一個類的實例被釋放之前,析構(gòu)函數(shù)被立即調(diào)用。用關(guān)鍵字deinit
來標(biāo)示析構(gòu)函數(shù),類似于初始化函數(shù)用init
來標(biāo)示。析構(gòu)函數(shù)只適用于類類型。
析構(gòu)過程原理
Swift 會自動釋放不再需要的實例以釋放資源。
Swift 通過自動引用計數(shù)(ARC)處理實例的內(nèi)存管理。
通常當(dāng)你的實例被釋放時不需要手動地去清理。但是,當(dāng)使用自己的資源時,你可能需要進(jìn)行一些額外的清理。
例如,如果創(chuàng)建了一個自定義的類來打開一個文件,并寫入一些數(shù)據(jù),你可能需要在類實例被釋放之前關(guān)閉該文件。
語法
在類的定義中,每個類最多只能有一個析構(gòu)函數(shù)。析構(gòu)函數(shù)不帶任何參數(shù),在寫法上不帶括號:
deinit {// 執(zhí)行析構(gòu)過程
}
實例
var counter = 0; // 引用計數(shù)器
class BaseClass {init() {counter += 1;}deinit {counter -= 1;}
}var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)
以上程序執(zhí)行輸出結(jié)果為:
1
0
當(dāng) show = nil 語句執(zhí)行后,計算器減去 1,show 占用的內(nèi)存就會釋放。
var counter = 0; // 引用計數(shù)器class BaseClass {init() {counter += 1;}deinit {counter -= 1;}
}var show: BaseClass? = BaseClass()print(counter)
print(counter)
以上程序執(zhí)行輸出結(jié)果為:
1
1
Swift 可選鏈
可選鏈(Optional Chaining)是一種可以請求和調(diào)用屬性、方法和子腳本的過程,用于請求或調(diào)用的目標(biāo)可能為nil。
可選鏈返回兩個值:
-
如果目標(biāo)有值,調(diào)用就會成功,返回該值
-
如果目標(biāo)為nil,調(diào)用將返回nil
多次請求或調(diào)用可以被鏈接成一個鏈,如果任意一個節(jié)點為nil將導(dǎo)致整條鏈?zhǔn)А?/span>
可選鏈可替代強(qiáng)制解析
通過在屬性、方法、或下標(biāo)腳本的可選值后面放一個問號(?),即可定義一個可選鏈。
可選鏈 '?' | 感嘆號(!)強(qiáng)制展開方法,屬性,下標(biāo)腳本可選鏈 |
? 放置于可選值后來調(diào)用方法,屬性,下標(biāo)腳本 | ! 放置于可選值后來調(diào)用方法,屬性,下標(biāo)腳本來強(qiáng)制展開值 |
當(dāng)可選為 nil 輸出比較友好的錯誤信息 | 當(dāng)可選為 nil 時強(qiáng)制展開執(zhí)行錯誤 |
使用感嘆號(!)可選鏈實例
class Person {var residence: Residence? }class Residence {var numberOfRooms = 1 }let john = Person()//將導(dǎo)致運(yùn)行時錯誤 let roomCount = john.residence!.numberOfRooms
以上程序執(zhí)行輸出結(jié)果為:
fatal error: unexpectedly found nil while unwrapping an Optional value
想使用感嘆號(!)強(qiáng)制解析獲得這個人residence屬性numberOfRooms屬性值,將會引發(fā)運(yùn)行時錯誤,因為這時沒有可以供解析的residence值。
使用問號(?)可選鏈實例
class Person { var residence: Residence? }class Residence {var numberOfRooms = 1 }let john = Person()// 鏈接可選residence?屬性,如果residence存在則取回numberOfRooms的值 if let roomCount = john.residence?.numberOfRooms {print("John 的房間號為 \(roomCount)。") } else {print("不能查看房間號") }
以上程序執(zhí)行輸出結(jié)果為:
不能查看房間號
因為這種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會返回Int?類型值,或者稱作"可選Int"。當(dāng)residence是空的時候(上例),選擇Int將會為空,因此會出現(xiàn)無法訪問numberOfRooms的情況。
要注意的是,即使numberOfRooms是非可選Int(Int?)時這一點也成立。只要是通過可選鏈的請求就意味著最后numberOfRooms總是返回一個Int?而不是Int。
為可選鏈定義模型類
你可以使用可選鏈來多層調(diào)用屬性,方法,和下標(biāo)腳本。這讓你可以利用它們之間的復(fù)雜模型來獲取更底層的屬性,并檢查是否可以成功獲取此類底層屬性。
實例
定義了四個模型類,其中包括多層可選鏈:
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}
通過可選鏈調(diào)用方法
你可以使用可選鏈的來調(diào)用可選值的方法并檢查方法調(diào)用是否成功。即使這個方法沒有返回值,你依然可以使用可選鏈來達(dá)成這一目的。
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()if ((john.residence?.printNumberOfRooms()) != nil) {print("輸出房間號")
} else {print("無法輸出房間號")
}
以上程序執(zhí)行輸出結(jié)果為:
無法輸出房間號
使用if語句來檢查是否能成功調(diào)用printNumberOfRooms方法:如果方法通過可選鏈調(diào)用成功,printNumberOfRooms的隱式返回值將會是Void,如果沒有成功,將返回nil。
使用可選鏈調(diào)用下標(biāo)腳本
你可以使用可選鏈來嘗試從下標(biāo)腳本獲取值并檢查下標(biāo)腳本的調(diào)用是否成功,然而,你不能通過可選鏈來設(shè)置下標(biāo)腳本。
實例1
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()
if let firstRoomName = john.residence?[0].name {print("第一個房間名 \(firstRoomName).")
} else {print("無法檢索到房間")
}
以上程序執(zhí)行輸出結(jié)果為:
無法檢索到房間
在下標(biāo)腳本調(diào)用中可選鏈的問號直接跟在 john.residence 的后面,在下標(biāo)腳本括號的前面,因為 john.residence 是可選鏈試圖獲得的可選值。
實例2
實例中創(chuàng)建一個 Residence 實例給 john.residence,且在他的 rooms 數(shù)組中有一個或多個 Room 實例,那么你可以使用可選鏈通過 Residence 下標(biāo)腳本來獲取在 rooms 數(shù)組中的實例了:
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouselet johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddressif let johnsStreet = john.residence?.address?.street {print("John 所在的街道是 \(johnsStreet)。")
} else {print("無法檢索到地址。 ")
}
以上程序執(zhí)行輸出結(jié)果為:
John 所在的街道是 Laurel Street。
通過可選鏈接調(diào)用來訪問下標(biāo)
通過可選鏈接調(diào)用,我們可以用下標(biāo)來對可選值進(jìn)行讀取或?qū)懭?#xff0c;并且判斷下標(biāo)調(diào)用是否成功。
實例
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouseif let firstRoomName = john.residence?[0].name {print("第一個房間名為\(firstRoomName)")
} else {print("無法檢索到房間")
}
以上程序執(zhí)行輸出結(jié)果為:
第一個房間名為客廳
訪問可選類型的下標(biāo)
如果下標(biāo)返回可空類型值,比如Swift中Dictionary的key下標(biāo)??梢栽谙聵?biāo)的閉合括號后面放一個問號來鏈接下標(biāo)的可空返回值:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] testScores["Dave"]?[0] = 91 testScores["Bev"]?[0]++ testScores["Brian"]?[0] = 72 // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
上面的例子中定義了一個testScores數(shù)組,包含了兩個鍵值對, 把String類型的key映射到一個整形數(shù)組。
這個例子用可選鏈接調(diào)用把"Dave"數(shù)組中第一個元素設(shè)為91,把"Bev"數(shù)組的第一個元素+1,然后嘗試把"Brian"數(shù)組中的第一個元素設(shè)為72。
前兩個調(diào)用是成功的,因為這兩個key存在。但是key"Brian"在字典中不存在,所以第三個調(diào)用失敗。
連接多層鏈接
你可以將多層可選鏈連接在一起,可以掘取模型內(nèi)更下層的屬性方法和下標(biāo)腳本。然而多層可選鏈不能再添加比已經(jīng)返回的可選值更多的層。?
如果你試圖通過可選鏈獲得Int值,不論使用了多少層鏈接返回的總是Int?。 相似的,如果你試圖通過可選鏈獲得Int?值,不論使用了多少層鏈接返回的總是Int?。
實例1
下面的例子試圖獲取john的residence屬性里的address的street屬性。這里使用了兩層可選鏈來聯(lián)系residence和address屬性,它們兩者都是可選類型:
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()if let johnsStreet = john.residence?.address?.street {print("John 的地址為 \(johnsStreet).")
} else {print("不能檢索地址")
}
以上程序執(zhí)行輸出結(jié)果為:
不能檢索地址
實例2
如果你為Address設(shè)定一個實例來作為john.residence.address的值,并為address的street屬性設(shè)定一個實際值,你可以通過多層可選鏈來得到這個屬性值。
class Person {var residence: Residence?
}class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {get{return rooms[i]}set {rooms[i] = newValue}}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}class Room {let name: Stringinit(name: String) { self.name = name }
}class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}
let john = Person()
john.residence?[0] = Room(name: "浴室")let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouseif let firstRoomName = john.residence?[0].name {print("第一個房間是\(firstRoomName)")
} else {print("無法檢索房間")
}
以上實例輸出結(jié)果為:
第一個房間是客廳
對返回可選值的函數(shù)進(jìn)行鏈接
我們還可以通過可選鏈接來調(diào)用返回可空值的方法,并且可以繼續(xù)對可選值進(jìn)行鏈接。
實例
class Person {var residence: Residence?
}// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數(shù)組
class Residence {var rooms = [Room]()var numberOfRooms: Int {return rooms.count}subscript(i: Int) -> Room {return rooms[i]}func printNumberOfRooms() {print("房間號為 \(numberOfRooms)")}var address: Address?
}// Room 定義一個name屬性和一個設(shè)定room名的初始化器
class Room {let name: Stringinit(name: String) { self.name = name }
}// 模型中的最終類叫做Address
class Address {var buildingName: String?var buildingNumber: String?var street: String?func buildingIdentifier() -> String? {if (buildingName != nil) {return buildingName} else if (buildingNumber != nil) {return buildingNumber} else {return nil}}
}let john = Person()if john.residence?.printNumberOfRooms() != nil {print("指定了房間號)")
} else {print("未指定房間號")
}
以上程序執(zhí)行輸出結(jié)果為:
未指定房間號
Swift 自動引用計數(shù)(ARC)
Swift 使用自動引用計數(shù)(ARC)這一機(jī)制來跟蹤和管理應(yīng)用程序的內(nèi)存
通常情況下我們不需要去手動釋放內(nèi)存,因為 ARC 會在類的實例不再被使用時,自動釋放其占用的內(nèi)存。
但在有些時候我們還是需要在代碼中實現(xiàn)內(nèi)存管理。
ARC 功能
-
當(dāng)每次使用 init() 方法創(chuàng)建一個類的新的實例的時候,ARC 會分配一大塊內(nèi)存用來儲存實例的信息。
-
內(nèi)存中會包含實例的類型信息,以及這個實例所有相關(guān)屬性的值。
-
當(dāng)實例不再被使用時,ARC 釋放實例所占用的內(nèi)存,并讓釋放的內(nèi)存能挪作他用。
-
為了確保使用中的實例不會被銷毀,ARC 會跟蹤和計算每一個實例正在被多少屬性,常量和變量所引用。
-
實例賦值給屬性、常量或變量,它們都會創(chuàng)建此實例的強(qiáng)引用,只要強(qiáng)引用還在,實例是不允許被銷毀的。
類實例之間的循環(huán)強(qiáng)引用
在上面的例子中,ARC 會跟蹤你所新創(chuàng)建的 Person 實例的引用數(shù)量,并且會在 Person 實例不再被需要時銷毀它。
然而,我們可能會寫出這樣的代碼,一個類永遠(yuǎn)不會有0個強(qiáng)引用。這種情況發(fā)生在兩個類實例互相保持對方的強(qiáng)引用,并讓對方不被銷毀。這就是所謂的循環(huán)強(qiáng)引用。
解決實例之間的循環(huán)強(qiáng)引用
Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環(huán)強(qiáng)引用問題:
- 弱引用
- 無主引用
弱引用和無主引用允許循環(huán)引用中的一個實例引用另外一個實例而不保持強(qiáng)引用。這樣實例能夠互相引用而不產(chǎn)生循環(huán)強(qiáng)引用。
對于生命周期中會變?yōu)閚il的實例使用弱引用。相反的,對于初始化賦值后再也不會被賦值為nil的實例,使用無主引用。
弱引用實例
class Module {let name: Stringinit(name: String) { self.name = name }var sub: SubModule?deinit { print("\(name) 主模塊") } }class SubModule {let number: Intinit(number: Int) { self.number = number } weak var topic: Module?deinit { print("子模塊 topic 數(shù)為 \(number)") } }var toc: Module? var list: SubModule? toc = Module(name: "ARC") list = SubModule(number: 4) toc!.sub = list list!.topic = toctoc = nil list = nil
以上程序執(zhí)行輸出結(jié)果為:
ARC 主模塊
子模塊 topic 數(shù)為 4
無主引用實例
class Student {let name: Stringvar section: Marks?init(name: String) {self.name = name}deinit { print("\(name)") } } class Marks {let marks: Int unowned let stname: Studentinit(marks: Int, stname: Student) {self.marks = marksself.stname = stname}deinit { print("學(xué)生的分?jǐn)?shù)為 \(marks)") } }var module: Student? module = Student(name: "ARC") module!.section = Marks(marks: 98, stname: module!) module = nil
以上程序執(zhí)行輸出結(jié)果為:
ARC
學(xué)生的分?jǐn)?shù)為 98
閉包引起的循環(huán)強(qiáng)引用
循環(huán)強(qiáng)引用還會發(fā)生在當(dāng)你將一個閉包賦值給類實例的某個屬性,并且這個閉包體中又使用了實例。這個閉包體中可能訪問了實例的某個屬性,例如self.someProperty,或者閉包中調(diào)用了實例的某個方法,例如self.someMethod。這兩種情況都導(dǎo)致了閉包 "捕獲" self,從而產(chǎn)生了循環(huán)強(qiáng)引用。
實例
下面的例子為你展示了當(dāng)一個閉包引用了self后是如何產(chǎn)生一個循環(huán)強(qiáng)引用的。例子中定義了一個叫HTMLElement的類,用一種簡單的模型表示 HTML 中的一個單獨的元素:
class HTMLElement {let name: Stringlet text: String?lazy var asHTML: () -> String = {if let text = self.text {return "<\(self.name)>\(text)</\(self.name)>"} else {return "<\(self.name) />"}}init(name: String, text: String? = nil) {self.name = nameself.text = text}deinit {print("\(name) is being deinitialized")}}// 創(chuàng)建實例并打印信息 var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML())
HTMLElement 類產(chǎn)生了類實例和 asHTML 默認(rèn)值的閉包之間的循環(huán)強(qiáng)引用。
實例的 asHTML 屬性持有閉包的強(qiáng)引用。但是,閉包在其閉包體內(nèi)使用了self(引用了self.name和self.text),因此閉包捕獲了self,這意味著閉包又反過來持有了HTMLElement實例的強(qiáng)引用。這樣兩個對象就產(chǎn)生了循環(huán)強(qiáng)引用。
解決閉包引起的循環(huán)強(qiáng)引用:在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環(huán)強(qiáng)引用。
弱引用和無主引用
當(dāng)閉包和捕獲的實例總是互相引用時并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為無主引用。
相反的,當(dāng)捕獲引用有時可能會是nil時,將閉包內(nèi)的捕獲定義為弱引用。
如果捕獲的引用絕對不會置為nil,應(yīng)該用無主引用,而不是弱引用。
實例
前面的HTMLElement例子中,無主引用是正確的解決循環(huán)強(qiáng)引用的方法。這樣編寫HTMLElement類來避免循環(huán)強(qiáng)引用:
class HTMLElement {let name: Stringlet text: String?lazy var asHTML: () -> String = { [unowned self] inif let text = self.text {return "<\(self.name)>\(text)</\(self.name)>"} else {return "<\(self.name) />"}}init(name: String, text: String? = nil) {self.name = nameself.text = text}deinit {print("\(name) 被析構(gòu)")}}//創(chuàng)建并打印HTMLElement實例 var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // HTMLElement實例將會被銷毀,并能看到它的析構(gòu)函數(shù)打印出的消息 paragraph = nil
以上程序執(zhí)行輸出結(jié)果為:
<p>hello, world</p>
p 被析構(gòu)
Swift 類型轉(zhuǎn)換
Swift 語言類型轉(zhuǎn)換可以判斷實例的類型。也可以用于檢測實例類型是否屬于其父類或者子類的實例。
Swift 中類型轉(zhuǎn)換使用 is 和 as 操作符實現(xiàn),is 用于檢測值的類型,as 用于轉(zhuǎn)換類型。
類型轉(zhuǎn)換也可以用來檢查一個類是否實現(xiàn)了某個協(xié)議。
檢查類型
類型轉(zhuǎn)換用于檢測實例類型是否屬于特定的實例類型。
你可以將它用在類和子類的層次結(jié)構(gòu)上,檢查特定類實例的類型并且轉(zhuǎn)換這個類實例的類型成為這個層次結(jié)構(gòu)中的其他類型。
類型檢查使用?is?關(guān)鍵字。
操作符?is?來檢查一個實例是否屬于特定子類型。若實例屬于那個子類型,類型檢查操作符返回 true,否則返回 false。
class Subjects {var physics: Stringinit(physics: String) {self.physics = physics} }class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations = equationssuper.init(physics: physics)} }class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae = formulaesuper.init(physics: physics)} }let sa = [Chemistry(physics: "固體物理", equations: "赫茲"),Maths(physics: "流體動力學(xué)", formulae: "千兆赫"),Chemistry(physics: "熱物理學(xué)", equations: "分貝"),Maths(physics: "天體物理學(xué)", formulae: "兆赫"),Maths(physics: "微分方程", formulae: "余弦級數(shù)")]let samplechem = Chemistry(physics: "固體物理", equations: "赫茲") print("實例物理學(xué)是: \(samplechem.physics)") print("實例方程式: \(samplechem.equations)")let samplemaths = Maths(physics: "流體動力學(xué)", formulae: "千兆赫") print("實例物理學(xué)是: \(samplemaths.physics)") print("實例公式是: \(samplemaths.formulae)")var chemCount = 0 var mathsCount = 0 for item in sa {// 如果是一個 Chemistry 類型的實例,返回 true,相反返回 false。if item is Chemistry {++chemCount} else if item is Maths {++mathsCount} }print("化學(xué)科目包含 \(chemCount) 個主題,數(shù)學(xué)包含 \(mathsCount) 個主題")
以上程序執(zhí)行輸出結(jié)果為:
實例物理學(xué)是: 固體物理 實例方程式: 赫茲 實例物理學(xué)是: 流體動力學(xué) 實例公式是: 千兆赫 化學(xué)科目包含 2 個主題,數(shù)學(xué)包含 3 個主題
向下轉(zhuǎn)型
向下轉(zhuǎn)型,用類型轉(zhuǎn)換操作符(as? 或 as!)
當(dāng)你不確定向下轉(zhuǎn)型可以成功時,用類型轉(zhuǎn)換的條件形式(as?)。條件形式的類型轉(zhuǎn)換總是返回一個可選值(optional value),并且若下轉(zhuǎn)是不可能的,可選值將是 nil。
只有你可以確定向下轉(zhuǎn)型一定會成功時,才使用強(qiáng)制形式(as!)。當(dāng)你試圖向下轉(zhuǎn)型為一個不正確的類型時,強(qiáng)制形式的類型轉(zhuǎn)換會觸發(fā)一個運(yùn)行時錯誤。
class Subjects {var physics: Stringinit(physics: String) {self.physics = physics}
}class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations = equationssuper.init(physics: physics)}
}class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae = formulaesuper.init(physics: physics)}
}let sa = [Chemistry(physics: "固體物理", equations: "赫茲"),Maths(physics: "流體動力學(xué)", formulae: "千兆赫"),Chemistry(physics: "熱物理學(xué)", equations: "分貝"),Maths(physics: "天體物理學(xué)", formulae: "兆赫"),Maths(physics: "微分方程", formulae: "余弦級數(shù)")]let samplechem = Chemistry(physics: "固體物理", equations: "赫茲")
print("實例物理學(xué)是: \(samplechem.physics)")
print("實例方程式: \(samplechem.equations)")let samplemaths = Maths(physics: "流體動力學(xué)", formulae: "千兆赫")
print("實例物理學(xué)是: \(samplemaths.physics)")
print("實例公式是: \(samplemaths.formulae)")var chemCount = 0
var mathsCount = 0for item in sa {// 類型轉(zhuǎn)換的條件形式if let show = item as? Chemistry {print("化學(xué)主題是: '\(show.physics)', \(show.equations)")// 強(qiáng)制形式} else if let example = item as? Maths {print("數(shù)學(xué)主題是: '\(example.physics)', \(example.formulae)")}
}
以上程序執(zhí)行輸出結(jié)果為:
實例物理學(xué)是: 固體物理
實例方程式: 赫茲
實例物理學(xué)是: 流體動力學(xué)
實例公式是: 千兆赫
化學(xué)主題是: '固體物理', 赫茲
數(shù)學(xué)主題是: '流體動力學(xué)', 千兆赫
化學(xué)主題是: '熱物理學(xué)', 分貝
數(shù)學(xué)主題是: '天體物理學(xué)', 兆赫
數(shù)學(xué)主題是: '微分方程', 余弦級數(shù)
Any和AnyObject的類型轉(zhuǎn)換
Swift為不確定類型提供了兩種特殊類型別名:
AnyObject
可以代表任何class類型的實例。(類似于Objective-C里的id)Any
可以表示任何類型,包括方法類型(function types)。
注意:
只有當(dāng)你明確的需要它的行為和功能時才使用Any
和AnyObject
。在你的代碼里使用你期望的明確的類型總是更好的。
Any 實例
class Subjects {var physics: Stringinit(physics: String) {self.physics = physics} }class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations = equationssuper.init(physics: physics)} }class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae = formulaesuper.init(physics: physics)} }let sa = [Chemistry(physics: "固體物理", equations: "赫茲"),Maths(physics: "流體動力學(xué)", formulae: "千兆赫"),Chemistry(physics: "熱物理學(xué)", equations: "分貝"),Maths(physics: "天體物理學(xué)", formulae: "兆赫"),Maths(physics: "微分方程", formulae: "余弦級數(shù)")]let samplechem = Chemistry(physics: "固體物理", equations: "赫茲") print("實例物理學(xué)是: \(samplechem.physics)") print("實例方程式: \(samplechem.equations)")let samplemaths = Maths(physics: "流體動力學(xué)", formulae: "千兆赫") print("實例物理學(xué)是: \(samplemaths.physics)") print("實例公式是: \(samplemaths.formulae)")var chemCount = 0 var mathsCount = 0for item in sa {// 類型轉(zhuǎn)換的條件形式if let show = item as? Chemistry {print("化學(xué)主題是: '\(show.physics)', \(show.equations)")// 強(qiáng)制形式} else if let example = item as? Maths {print("數(shù)學(xué)主題是: '\(example.physics)', \(example.formulae)")} }// 可以存儲Any類型的數(shù)組 exampleany var exampleany = [Any]()exampleany.append(12) exampleany.append(3.14159) exampleany.append("Any 實例") exampleany.append(Chemistry(physics: "固體物理", equations: "兆赫"))for item2 in exampleany {switch item2 {case let someInt as Int:print("整型值為 \(someInt)")case let someDouble as Double where someDouble > 0:print("Pi 值為 \(someDouble)")case let someString as String:print("\(someString)")case let phy as Chemistry:print("主題 '\(phy.physics)', \(phy.equations)")default:print("None")} }
以上程序執(zhí)行輸出結(jié)果為:
實例物理學(xué)是: 固體物理
實例方程式: 赫茲
實例物理學(xué)是: 流體動力學(xué)
實例公式是: 千兆赫
化學(xué)主題是: '固體物理', 赫茲
數(shù)學(xué)主題是: '流體動力學(xué)', 千兆赫
化學(xué)主題是: '熱物理學(xué)', 分貝
數(shù)學(xué)主題是: '天體物理學(xué)', 兆赫
數(shù)學(xué)主題是: '微分方程', 余弦級數(shù)
整型值為 12
Pi 值為 3.14159
Any 實例
主題 '固體物理', 兆赫
AnyObject 實例
class Subjects {var physics: Stringinit(physics: String) {self.physics = physics} }class Chemistry: Subjects {var equations: Stringinit(physics: String, equations: String) {self.equations = equationssuper.init(physics: physics)} }class Maths: Subjects {var formulae: Stringinit(physics: String, formulae: String) {self.formulae = formulaesuper.init(physics: physics)} }// [AnyObject] 類型的數(shù)組 let saprint: [AnyObject] = [Chemistry(physics: "固體物理", equations: "赫茲"),Maths(physics: "流體動力學(xué)", formulae: "千兆赫"),Chemistry(physics: "熱物理學(xué)", equations: "分貝"),Maths(physics: "天體物理學(xué)", formulae: "兆赫"),Maths(physics: "微分方程", formulae: "余弦級數(shù)")]let samplechem = Chemistry(physics: "固體物理", equations: "赫茲") print("實例物理學(xué)是: \(samplechem.physics)") print("實例方程式: \(samplechem.equations)")let samplemaths = Maths(physics: "流體動力學(xué)", formulae: "千兆赫") print("實例物理學(xué)是: \(samplemaths.physics)") print("實例公式是: \(samplemaths.formulae)")var chemCount = 0 var mathsCount = 0for item in saprint {// 類型轉(zhuǎn)換的條件形式if let show = item as? Chemistry {print("化學(xué)主題是: '\(show.physics)', \(show.equations)")// 強(qiáng)制形式} else if let example = item as? Maths {print("數(shù)學(xué)主題是: '\(example.physics)', \(example.formulae)")} }var exampleany = [Any]() exampleany.append(12) exampleany.append(3.14159) exampleany.append("Any 實例") exampleany.append(Chemistry(physics: "固體物理", equations: "兆赫"))for item2 in exampleany {switch item2 {case let someInt as Int:print("整型值為 \(someInt)")case let someDouble as Double where someDouble > 0:print("Pi 值為 \(someDouble)")case let someString as String:print("\(someString)")case let phy as Chemistry:print("主題 '\(phy.physics)', \(phy.equations)")default:print("None")} }
以上程序執(zhí)行輸出結(jié)果為:
實例物理學(xué)是: 固體物理
實例方程式: 赫茲
實例物理學(xué)是: 流體動力學(xué)
實例公式是: 千兆赫
化學(xué)主題是: '固體物理', 赫茲
數(shù)學(xué)主題是: '流體動力學(xué)', 千兆赫
化學(xué)主題是: '熱物理學(xué)', 分貝
數(shù)學(xué)主題是: '天體物理學(xué)', 兆赫
數(shù)學(xué)主題是: '微分方程', 余弦級數(shù)
整型值為 12
Pi 值為 3.14159
Any 實例
主題 '固體物理', 兆赫
在一個switch語句的case中使用強(qiáng)制形式的類型轉(zhuǎn)換操作符(as, 而不是 as?)來檢查和轉(zhuǎn)換到一個明確的類型。
Swift 擴(kuò)展
擴(kuò)展就是向一個已有的類、結(jié)構(gòu)體或枚舉類型添加新功能。
擴(kuò)展可以對一個類型添加新的功能,但是不能重寫已有的功能。
Swift 中的擴(kuò)展可以:
- 添加計算型屬性和計算型靜態(tài)屬性
- 定義實例方法和類型方法
- 提供新的構(gòu)造器
- 定義下標(biāo)
- 定義和使用新的嵌套類型
- 使一個已有類型符合某個協(xié)議
語法
擴(kuò)展聲明使用關(guān)鍵字?extension:
extension SomeType {// 加到SomeType的新功能寫到這里
}
一個擴(kuò)展可以擴(kuò)展一個已有類型,使其能夠適配一個或多個協(xié)議,語法格式如下:
extension SomeType: SomeProtocol, AnotherProctocol {// 協(xié)議實現(xiàn)寫到這里
}
計算型屬性
擴(kuò)展可以向已有類型添加計算型實例屬性和計算型類型屬性。
實例
下面的例子向 Int 類型添加了 5 個計算型實例屬性并擴(kuò)展其功能:
extension Int {var add: Int {return self + 100 }var sub: Int { return self - 10 }var mul: Int { return self * 10 }var div: Int { return self / 5 }
}let addition = 3.add
print("加法運(yùn)算后的值:\(addition)")let subtraction = 120.sub
print("減法運(yùn)算后的值:\(subtraction)")let multiplication = 39.mul
print("乘法運(yùn)算后的值:\(multiplication)")let division = 55.div
print("除法運(yùn)算后的值: \(division)")let mix = 30.add + 34.sub
print("混合運(yùn)算結(jié)果:\(mix)")
以上程序執(zhí)行輸出結(jié)果為:
加法運(yùn)算后的值:103
減法運(yùn)算后的值:110
乘法運(yùn)算后的值:390
除法運(yùn)算后的值: 11
混合運(yùn)算結(jié)果:154
構(gòu)造器
擴(kuò)展可以向已有類型添加新的構(gòu)造器。
這可以讓你擴(kuò)展其它類型,將你自己的定制類型作為構(gòu)造器參數(shù),或者提供該類型的原始實現(xiàn)中沒有包含的額外初始化選項。
擴(kuò)展可以向類中添加新的便利構(gòu)造器 init(),但是它們不能向類中添加新的指定構(gòu)造器或析構(gòu)函數(shù) deinit() 。
struct sum {var num1 = 100, num2 = 200
}struct diff {var no1 = 200, no2 = 100
}struct mult {var a = sum()var b = diff()
}extension mult {init(x: sum, y: diff) {_ = x.num1 + x.num2_ = y.no1 + y.no2}
}let a = sum(num1: 100, num2: 200)
let b = diff(no1: 200, no2: 100)let getMult = mult(x: a, y: b)
print("getMult sum\(getMult.a.num1, getMult.a.num2)")
print("getMult diff\(getMult.b.no1, getMult.b.no2)")
以上程序執(zhí)行輸出結(jié)果為:
getMult sum(100, 200)
getMult diff(200, 100)
方法
擴(kuò)展可以向已有類型添加新的實例方法和類型方法。
下面的例子向Int類型添加一個名為 topics 的新實例方法:
extension Int {
func topics(summation: () -> ()) {for _ in 0..<self {summation() }}
} 4.topics({print("擴(kuò)展模塊內(nèi)")
}) 3.topics({print("內(nèi)型轉(zhuǎn)換模塊內(nèi)")
})
以上程序執(zhí)行輸出結(jié)果為:
擴(kuò)展模塊內(nèi)
擴(kuò)展模塊內(nèi)
擴(kuò)展模塊內(nèi)
擴(kuò)展模塊內(nèi)
內(nèi)型轉(zhuǎn)換模塊內(nèi)
內(nèi)型轉(zhuǎn)換模塊內(nèi)
內(nèi)型轉(zhuǎn)換模塊內(nèi)
這個topics
方法使用了一個() -> ()
類型的單參數(shù),表明函數(shù)沒有參數(shù)而且沒有返回值。
定義該擴(kuò)展之后,你就可以對任意整數(shù)調(diào)用?topics
?方法,實現(xiàn)的功能則是多次執(zhí)行某任務(wù):
可變實例方法
通過擴(kuò)展添加的實例方法也可以修改該實例本身。
結(jié)構(gòu)體和枚舉類型中修改self或其屬性的方法必須將該實例方法標(biāo)注為mutating,正如來自原始實現(xiàn)的修改方法一樣。
實例
下面的例子向 Swift 的 Double 類型添加了一個新的名為 square 的修改方法,來實現(xiàn)一個原始值的平方計算:
extension Double { mutating func square() {let pi = 3.1415self = pi * self * self} }var Trial1 = 3.3 Trial1.square() print("圓的面積為: \(Trial1)")var Trial2 = 5.8 Trial2.square() print("圓的面積為: \(Trial2)")var Trial3 = 120.3 Trial3.square() print("圓的面積為: \(Trial3)")
以上程序執(zhí)行輸出結(jié)果為:
圓的面積為: 34.210935
圓的面積為: 105.68006
圓的面積為: 45464.070735
下標(biāo)
擴(kuò)展可以向一個已有類型添加新下標(biāo)。
實例
以下例子向 Swift 內(nèi)建類型Int添加了一個整型下標(biāo)。該下標(biāo)[n]返回十進(jìn)制數(shù)字
extension Int { subscript(var multtable: Int) -> Int {var no1 = 1while multtable > 0 {no1 *= 10--multtable}return (self / no1) % 10} }print(12[0]) print(7869[1]) print(786543[2])
以上程序執(zhí)行輸出結(jié)果為:
2
6
5
嵌套類型
擴(kuò)展可以向已有的類、結(jié)構(gòu)體和枚舉添加新的嵌套類型:
extension Int {enum calc{case addcase subcase multcase divcase anything}var print: calc {switch self{case 0:return .addcase 1:return .subcase 2:return .multcase 3:return .divdefault:return .anything}}
}func result(numb: [Int]) {for i in numb {switch i.print {case .add:print(" 10 ")case .sub:print(" 20 ")case .mult:print(" 30 ")case .div:print(" 40 ")default:print(" 50 ")}}
}result([0, 1, 2, 3, 4, 7])
以上程序執(zhí)行輸出結(jié)果為:
10 20 30 40 50 50
筆記:???????
擴(kuò)展下標(biāo)文中的代碼對于較高版本的swift可能會報錯:
'var' in this position is interpreted as an argument label Left side of mutating operator isn't mutable: 'multtable' is immutable
驗證了寫法,這樣寫可以避免問題:
extension Int{
subscript(digitIndex:Int)->Int{var decimalBase = 1var digit = digitIndex// 不能直接使用digitIndex,會報錯while digit > 0 {decimalBase *= 10digit = digit - 1}return (self/decimalBase) % 10}
}print(12[0])
print(7869[1])
print(786543[2])
參考了網(wǎng)上的寫法,還可以這樣寫:
extension Int{subscript(digitIndex:Int)->Int{var decimalBase = 1for _ in 0 ..< digitIndex{decimalBase *= 10}return (self/decimalBase) % 10}
}
print(12[0])
print(7869[1])
print(786543[2])
Swift 協(xié)議
協(xié)議規(guī)定了用來實現(xiàn)某一特定功能所必需的方法和屬性。
任意能夠滿足協(xié)議要求的類型被稱為遵循(conform)這個協(xié)議。
類,結(jié)構(gòu)體或枚舉類型都可以遵循協(xié)議,并提供具體實現(xiàn)來完成協(xié)議定義的方法和功能。
語法
協(xié)議的語法格式如下:
protocol SomeProtocol {// 協(xié)議內(nèi)容
}
要使類遵循某個協(xié)議,需要在類型名稱后加上協(xié)議名稱,中間以冒號:分隔,作為類型定義的一部分。遵循多個協(xié)議時,各協(xié)議之間用逗號,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {// 結(jié)構(gòu)體內(nèi)容
}
如果類在遵循協(xié)議的同時擁有父類,應(yīng)該將父類名放在協(xié)議名之前,以逗號分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {// 類的內(nèi)容
}
對屬性的規(guī)定
協(xié)議用于指定特定的實例屬性或類屬性,而不用指定是存儲型屬性或計算型屬性。此外還必須指明是只讀的還是可讀可寫的。
協(xié)議中的通常用var來聲明變量屬性,在類型聲明后加上{ set get }來表示屬性是可讀可寫的,只讀屬性則用{ get }來表示。
protocol classa {var marks: Int { get set }var result: Bool { get }func attendance() -> Stringfunc markssecured() -> String}protocol classb: classa {var present: Bool { get set }var subject: String { get set }var stname: String { get set }}class classc: classb {var marks = 96let result = truevar present = falsevar subject = "Swift 協(xié)議"var stname = "Protocols"func attendance() -> String {return "The \(stname) has secured 99% attendance"}func markssecured() -> String {return "\(stname) has scored \(marks)"} }let studdet = classc() studdet.stname = "Swift" studdet.marks = 98 studdet.markssecured()print(studdet.marks) print(studdet.result) print(studdet.present) print(studdet.subject) print(studdet.stname)
以上程序執(zhí)行輸出結(jié)果為:
98
true
false
Swift 協(xié)議
Swift
對 Mutating 方法的規(guī)定
有時需要在方法中改變它的實例。
例如,值類型(結(jié)構(gòu)體,枚舉)的實例方法中,將mutating關(guān)鍵字作為函數(shù)的前綴,寫在func之前,表示可以在該方法中修改它所屬的實例及其實例屬性的值。
protocol daysofaweek {mutating func show()
}enum days: daysofaweek {case sun, mon, tue, wed, thurs, fri, satmutating func show() {switch self {case .sun:self = .sunprint("Sunday")case .mon:self = .monprint("Monday")case .tue:self = .tueprint("Tuesday")case .wed:self = .wedprint("Wednesday")case .thurs:self = .thursprint("Wednesday")case .fri:self = .friprint("Firday")case .sat:self = .satprint("Saturday")default:print("NO Such Day")}}
}var res = days.wed
res.show()
以上程序執(zhí)行輸出結(jié)果為:
Wednesday
對構(gòu)造器的規(guī)定
協(xié)議可以要求它的遵循者實現(xiàn)指定的構(gòu)造器。
你可以像書寫普通的構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號和構(gòu)造器的實體,語法如下:
protocol SomeProtocol {init(someParameter: Int)
}
實例
protocol tcpprotocol {init(aprot: Int)
}
協(xié)議構(gòu)造器規(guī)定在類中的實現(xiàn)
你可以在遵循該協(xié)議的類中實現(xiàn)構(gòu)造器,并指定其為類的指定構(gòu)造器或者便利構(gòu)造器。在這兩種情況下,你都必須給構(gòu)造器實現(xiàn)標(biāo)上"required"修飾符:
class SomeClass: SomeProtocol {required init(someParameter: Int) {// 構(gòu)造器實現(xiàn)}
}protocol tcpprotocol {init(aprot: Int)
}class tcpClass: tcpprotocol {required init(aprot: Int) {}
}
使用required修飾符可以保證:所有的遵循該協(xié)議的子類,同樣能為構(gòu)造器規(guī)定提供一個顯式的實現(xiàn)或繼承實現(xiàn)。
如果一個子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器遵循了某個協(xié)議的規(guī)定,那么該構(gòu)造器的實現(xiàn)需要被同時標(biāo)示required和override修飾符:
protocol tcpprotocol {init(no1: Int) }class mainClass {var no1: Int // 局部變量init(no1: Int) {self.no1 = no1 // 初始化} }class subClass: mainClass, tcpprotocol {var no2: Intinit(no1: Int, no2 : Int) {self.no2 = no2super.init(no1:no1)} // 因為遵循協(xié)議,需要加上"required"; 因為繼承自父類,需要加上"override"required override convenience init(no1: Int) {self.init(no1:no1, no2:0)} } let res = mainClass(no1: 20) let show = subClass(no1: 30, no2: 50)print("res is: \(res.no1)") print("res is: \(show.no1)") print("res is: \(show.no2)")
以上程序執(zhí)行輸出結(jié)果為:
res is: 20
res is: 30
res is: 50
協(xié)議類型
盡管協(xié)議本身并不實現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做類型來使用。
協(xié)議可以像其他普通類型一樣使用,使用場景:
- 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量、變量或?qū)傩缘念愋?/span>
- 作為數(shù)組、字典或其他容器中的元素類型
實例
protocol Generator {associatedtype membersfunc next() -> members?
}var items = [10,20,30].makeIterator()
while let x = items.next() {print(x)
}for lists in [1,2,3].map( {i in i*5}) {print(lists)
}print([100,200,300])
print([1,2,3].map({i in i*10}))
以上程序執(zhí)行輸出結(jié)果為:
10
20
30
5
10
15
[100, 200, 300]
[10, 20, 30]
在擴(kuò)展中添加協(xié)議成員
我們可以可以通過擴(kuò)展來擴(kuò)充已存在類型( 類,結(jié)構(gòu)體,枚舉等)。
擴(kuò)展可以為已存在的類型添加屬性,方法,下標(biāo)腳本,協(xié)議等成員。
protocol AgeClasificationProtocol {var age: Int { get }func agetype() -> String
}class Person {let firstname: Stringlet lastname: Stringvar age: Intinit(firstname: String, lastname: String) {self.firstname = firstnameself.lastname = lastnameself.age = 10}
}extension Person : AgeClasificationProtocol {func fullname() -> String {var c: Stringc = firstname + " " + lastnamereturn c}func agetype() -> String {switch age {case 0...2:return "Baby"case 2...12:return "Child"case 13...19:return "Teenager"case let x where x > 65:return "Elderly"default:return "Normal"}}
}
協(xié)議的繼承
協(xié)議能夠繼承一個或多個其他協(xié)議,可以在繼承的協(xié)議基礎(chǔ)上增加新的內(nèi)容要求。
協(xié)議的繼承語法與類的繼承相似,多個被繼承的協(xié)議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {// 協(xié)議定義
}
實例
protocol Classa {var no1: Int { get set }func calc(sum: Int)
}protocol Result {func print(target: Classa)
}class Student2: Result {func print(target: Classa) {target.calc(1)}
}class Classb: Result {func print(target: Classa) {target.calc(5)}
}class Student: Classa {var no1: Int = 10func calc(sum: Int) {no1 -= sumprint("學(xué)生嘗試 \(sum) 次通過")if no1 <= 0 {print("學(xué)生缺席考試")}}
}class Player {var stmark: Result!init(stmark: Result) {self.stmark = stmark}func print(target: Classa) {stmark.print(target)}
}var marks = Player(stmark: Student2())
var marksec = Student()marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
marks.stmark = Classb()
marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
以上程序執(zhí)行輸出結(jié)果為:
學(xué)生嘗試 1 次通過
學(xué)生嘗試 1 次通過
學(xué)生嘗試 1 次通過
學(xué)生嘗試 5 次通過
學(xué)生嘗試 5 次通過
學(xué)生缺席考試
學(xué)生嘗試 5 次通過
學(xué)生缺席考試
類專屬協(xié)議
你可以在協(xié)議的繼承列表中,通過添加class關(guān)鍵字,限制協(xié)議只能適配到類(class)類型。
該class關(guān)鍵字必須是第一個出現(xiàn)在協(xié)議的繼承列表中,其后,才是其他繼承協(xié)議。格式如下:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {// 協(xié)議定義
}
實例
protocol TcpProtocol {init(no1: Int) }class MainClass {var no1: Int // 局部變量init(no1: Int) {self.no1 = no1 // 初始化} }class SubClass: MainClass, TcpProtocol {var no2: Intinit(no1: Int, no2 : Int) {self.no2 = no2super.init(no1:no1)}// 因為遵循協(xié)議,需要加上"required"; 因為繼承自父類,需要加上"override"required override convenience init(no1: Int) {self.init(no1:no1, no2:0)} } let res = MainClass(no1: 20) let show = SubClass(no1: 30, no2: 50)print("res is: \(res.no1)") print("res is: \(show.no1)") print("res is: \(show.no2)")
以上程序執(zhí)行輸出結(jié)果為:
res is: 20
res is: 30
res is: 50
協(xié)議合成
Swift 支持合成多個協(xié)議,這在我們需要同時遵循多個協(xié)議時非常有用。
語法格式如下:
protocol Stname {var name: String { get } }protocol Stage {var age: Int { get } }struct Person: Stname, Stage {var name: Stringvar age: Int }func show(celebrator: Stname & Stage) {print("\(celebrator.name) is \(celebrator.age) years old") }let studname = Person(name: "Priya", age: 21) show(studname)let stud = Person(name: "Rehan", age: 29) print(stud)let student = Person(name: "Roshan", age: 19) print(student)
以上程序執(zhí)行輸出結(jié)果為:
Priya is 21 years old
Person(name: "Rehan", age: 29)
Person(name: "Roshan", age: 19)
檢驗協(xié)議的一致性
你可以使用is和as操作符來檢查是否遵循某一協(xié)議或強(qiáng)制轉(zhuǎn)化為某一類型。
is
操作符用來檢查實例是否遵循
了某個協(xié)議
。as?
返回一個可選值,當(dāng)實例遵循
協(xié)議時,返回該協(xié)議類型;否則返回nil
。as
用以強(qiáng)制向下轉(zhuǎn)型,如果強(qiáng)轉(zhuǎn)失敗,會引起運(yùn)行時錯誤。
實例
下面的例子定義了一個 HasArea 的協(xié)議,要求有一個Double類型可讀的 area:
protocol HasArea {var area: Double { get }
}// 定義了Circle類,都遵循了HasArea協(xié)議
class Circle: HasArea {let pi = 3.1415927var radius: Doublevar area: Double { return pi * radius * radius }init(radius: Double) { self.radius = radius }
}// 定義了Country類,都遵循了HasArea協(xié)議
class Country: HasArea {var area: Doubleinit(area: Double) { self.area = area }
}// Animal是一個沒有實現(xiàn)HasArea協(xié)議的類
class Animal {var legs: Intinit(legs: Int) { self.legs = legs }
}let objects: [AnyObject] = [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)
]for object in objects {// 對迭代出的每一個元素進(jìn)行檢查,看它是否遵循了HasArea協(xié)議if let objectWithArea = object as? HasArea {print("面積為 \(objectWithArea.area)")} else {print("沒有面積")}
}
以上程序執(zhí)行輸出結(jié)果為:
面積為 12.5663708
面積為 243610.0
沒有面積
Swift 泛型
Swift 提供了泛型讓你寫出靈活且可重用的函數(shù)和類型。
Swift 標(biāo)準(zhǔn)庫是通過泛型代碼構(gòu)建出來的。
Swift 的數(shù)組和字典類型都是泛型集。
你可以創(chuàng)建一個Int數(shù)組,也可創(chuàng)建一個String數(shù)組,或者甚至于可以是任何其他 Swift 的類型數(shù)據(jù)數(shù)組。
以下實例是一個非泛型函數(shù) exchange 用來交換兩個 Int 值:
實例
// 定義一個交換兩個變量的函數(shù)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
? ? let temporaryA = a
? ? a = b
? ? b = temporaryA
}
?
var numb1 = 100
var numb2 = 200
?
print("交換前數(shù)據(jù): \(numb1) 和 \(numb2)")
swapTwoInts(&numb1, &numb2)
print("交換后數(shù)據(jù): \(numb1) 和 \(numb2)")
以上程序執(zhí)行輸出結(jié)果為:
交換前數(shù)據(jù): 100 和 200
交換后數(shù)據(jù): 200 和 100
以上實例只試用與交換整數(shù) Int 類型的變量。如果你想要交換兩個 String 值或者 Double 值,就得重新寫個對應(yīng)的函數(shù),例如 swapTwoStrings(_:_:) 和 swapTwoDoubles(_:_:),如下所示:
String 和 Double 值交換函數(shù)
func swapTwoStrings(_ a: inout String, _ b: inout String) {
? ? let temporaryA = a
? ? a = b
? ? b = temporaryA
}
?
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
? ? let temporaryA = a
? ? a = b
? ? b = temporaryA
}
從以上代碼來看,它們功能代碼是相同的,只是類型上不一樣,這時我們可以使用泛型,從而避免重復(fù)編寫代碼。
泛型使用了占位類型名(在這里用字母 T 來表示)來代替實際類型名(例如 Int、String 或 Double)。
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
swapTwoValues 后面跟著占位類型名(T),并用尖括號括起來(<T>
)。這個尖括號告訴 Swift 那個 T 是 swapTwoValues(_:_:) 函數(shù)定義內(nèi)的一個占位類型名,因此 Swift 不會去查找名為 T 的實際類型。
以下實例是一個泛型函數(shù) exchange 用來交換兩個 Int 和 String 值:
實例
// 定義一個交換兩個變量的函數(shù)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
? ? let temporaryA = a
? ? a = b
? ? b = temporaryA
}
?
var numb1 = 100
var numb2 = 200
?
print("交換前數(shù)據(jù): ?\(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交換后數(shù)據(jù): \(numb1) 和 \(numb2)")
?
var str1 = "A"
var str2 = "B"
?
print("交換前數(shù)據(jù): ?\(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交換后數(shù)據(jù): \(str1) 和 \(str2)")
以上程序執(zhí)行輸出結(jié)果為:
交換前數(shù)據(jù): 100 和 200 交換后數(shù)據(jù): 200 和 100 交換前數(shù)據(jù): A 和 B 交換后數(shù)據(jù): B 和 A
泛型類型
Swift 允許你定義你自己的泛型類型。
自定義類、結(jié)構(gòu)體和枚舉作用于任何類型,如同 Array 和 Dictionary 的用法。
接下來我們來編寫一個名為 Stack (棧)的泛型集合類型,棧只允許在集合的末端添加新的元素(稱之為入棧),且也只能從末端移除元素(稱之為出棧)。
接下來我們來編寫一個名為 Stack (棧)的泛型集合類型,棧只允許在集合的末端添加新的元素(稱之為入棧),且也只能從末端移除元素(稱之為出棧)。
圖片中從左到右解析如下:
- 三個值在棧中。
- 第四個值被壓入到棧的頂部。
- 現(xiàn)在有四個值在棧中,最近入棧的那個值在頂部。
- 棧中最頂部的那個值被移除,或稱之為出棧。
- 移除掉一個值后,現(xiàn)在棧又只有三個值了。
以下實例是一個非泛型版本的棧,以 Int 型的棧為例:
Int 型的棧
struct IntStack {
? ? var items = [Int]()
? ? mutating func push(_ item: Int) {
? ? ? ? items.append(item)
? ? }
? ? mutating func pop() -> Int {
? ? ? ? return items.removeLast()
? ? }
}
這個結(jié)構(gòu)體在棧中使用一個名為 items 的 Array 屬性來存儲值。Stack 提供了兩個方法:push(_:) 和 pop(),用來向棧中壓入值以及從棧中移除值。這些方法被標(biāo)記為 mutating,因為它們需要修改結(jié)構(gòu)體的 items 數(shù)組。
上面的 IntStack 結(jié)構(gòu)體只能用于 Int 類型。不過,可以定義一個泛型 Stack 結(jié)構(gòu)體,從而能夠處理任意類型的值。
下面是相同代碼的泛型版本:
泛型的棧
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
? ?items.append(item)
}
?mutating func pop() -> Element {
? return items.removeLast()
? }
}
var stackOfStrings = Stack<String>()
print("字符串元素入棧: ")
stackOfStrings.push("google")
stackOfStrings.push("runoob")
print(stackOfStrings.items);
let deletetos = stackOfStrings.pop()
print("出棧元素: " + deletetos)
var stackOfInts = Stack<Int>()
print("整數(shù)元素入棧: ")
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);
實例執(zhí)行結(jié)果為:
字符串元素入棧:
["google", "runoob"]
出棧元素: runoob
整數(shù)元素入棧:
[1, 2]
Stack 基本上和 IntStack 相同,占位類型參數(shù) Element 代替了實際的 Int 類型。
以上實例中 Element 在如下三個地方被用作占位符:
- 創(chuàng)建?items?屬性,使用?Element?類型的空數(shù)組對其進(jìn)行初始化。
- 指定?push(_:)?方法的唯一參數(shù)?item?的類型必須是?Element?類型。
- 指定?pop()?方法的返回值類型必須是?Element?類型。
擴(kuò)展泛型類型
當(dāng)你擴(kuò)展一個泛型類型的時候(使用 extension 關(guān)鍵字),你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表。更加方便的是,原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展里是可以使用的,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用。
下面的例子擴(kuò)展了泛型類型 Stack,為其添加了一個名為 topItem 的只讀計算型屬性,它將會返回當(dāng)前棧頂端的元素而不會將其從棧中移除:
泛型
struct Stack<Element> {
? ? var items = [Element]()
? ? mutating func push(_ item: Element) {
? ? ? ? items.append(item)
? ? }
? ? mutating func pop() -> Element {
? ? ? ? return items.removeLast()
? ? }
}
?
extension Stack {
? ? var topItem: Element? {
? ? ? ?return items.isEmpty ? nil : items[items.count - 1]
? ? }
}
?
var stackOfStrings = Stack<String>()
print("字符串元素入棧: ")
stackOfStrings.push("google")
stackOfStrings.push("runoob")
?
if let topItem = stackOfStrings.topItem {
? ? print("棧中的頂部元素是:\(topItem).")
}
?
print(stackOfStrings.items)
實例中 topItem 屬性會返回一個 Element 類型的可選值。當(dāng)棧為空的時候,topItem 會返回 nil;當(dāng)棧不為空的時候,topItem 會返回 items 數(shù)組中的最后一個元素。
以上程序執(zhí)行輸出結(jié)果為:
字符串元素入棧:
棧中的頂部元素是:runoob.
["google", "runoob"]
我們也可以通過擴(kuò)展一個存在的類型來指定關(guān)聯(lián)類型。
例如 Swift 的 Array 類型已經(jīng)提供 append(_:) 方法,一個 count 屬性,以及一個接受 Int 類型索引值的下標(biāo)用以檢索其元素。這三個功能都符合 Container 協(xié)議的要求,所以你只需簡單地聲明 Array 采納該協(xié)議就可以擴(kuò)展 Array。
以下實例創(chuàng)建一個空擴(kuò)展即可:
extension Array: Container {}
類型約束
類型約束指定了一個必須繼承自指定類的類型參數(shù),或者遵循一個特定的協(xié)議或協(xié)議構(gòu)成。
類型約束語法
你可以寫一個在一個類型參數(shù)名后面的類型約束,通過冒號分割,來作為類型參數(shù)鏈的一部分。這種作用于泛型函數(shù)的類型約束的基礎(chǔ)語法如下所示(和泛型類型的語法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {// 這里是泛型函數(shù)的函數(shù)體部分
}
上面這個函數(shù)有兩個類型參數(shù)。第一個類型參數(shù) T,有一個要求 T 必須是 SomeClass 子類的類型約束;第二個類型參數(shù) U,有一個要求 U 必須符合 SomeProtocol 協(xié)議的類型約束。
實例
泛型
// 非泛型函數(shù),查找指定字符串在數(shù)組中的索引
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
? ? for (index, value) in array.enumerated() {
? ? ? ? if value == valueToFind {
? ? ? ? ? ? // 找到返回索引值
? ? ? ? ? ? return index
? ? ? ? }
? ? }
? ? return nil
}
?
?
let strings = ["google", "weibo", "taobao", "runoob", "facebook"]
if let foundIndex = findIndex(ofString: "runoob", in: strings) {
? ? print("runoob 的索引為 \(foundIndex)")
}
索引下標(biāo)從 0 開始。
以上程序執(zhí)行輸出結(jié)果為:
runoob 的索引為 3
關(guān)聯(lián)類
Swift 中使用 associatedtype 關(guān)鍵字來設(shè)置關(guān)聯(lián)類型實例。
下面例子定義了一個 Container 協(xié)議,該協(xié)議定義了一個關(guān)聯(lián)類型 ItemType。
Container 協(xié)議只指定了三個任何遵從 Container 協(xié)議的類型必須提供的功能。遵從協(xié)議的類型在滿足這三個條件的情況下也可以提供其他額外的功能。
// Container 協(xié)議
protocol Container {associatedtype ItemType// 添加一個新元素到容器里mutating func append(_ item: ItemType)// 獲取容器中元素的數(shù)var count: Int { get }// 通過索引值類型為 Int 的下標(biāo)檢索到容器中的每一個元素subscript(i: Int) -> ItemType { get }
}// Stack 結(jié)構(gòu)體遵從 Container 協(xié)議
struct Stack<Element>: Container {// Stack<Element> 的原始實現(xiàn)部分var items = [Element]()mutating func push(_ item: Element) {items.append(item)}mutating func pop() -> Element {return items.removeLast()}// Container 協(xié)議的實現(xiàn)部分mutating func append(_ item: Element) {self.push(item)}var count: Int {return items.count}subscript(i: Int) -> Element {return items[i]}
}var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素個數(shù)
print( tos.count)
以上程序執(zhí)行輸出結(jié)果為:
["google", "runoob", "taobao"]
3
Where 語句
類型約束能夠確保類型符合泛型函數(shù)或類的定義約束。
你可以在參數(shù)列表中通過where語句定義參數(shù)的約束。
你可以寫一個where語句,緊跟在在類型參數(shù)列表后面,where語句后跟一個或者多個針對關(guān)聯(lián)類型的約束,以及(或)一個或多個類型和關(guān)聯(lián)類型間的等價(equality)關(guān)系。
實例
下面的例子定義了一個名為allItemsMatch的泛型函數(shù),用來檢查兩個Container實例是否包含相同順序的相同元素。
如果所有的元素能夠匹配,那么返回 true,反之則返回 false。
泛型
// Container 協(xié)議
protocol Container {
? ? associatedtype ItemType
? ? // 添加一個新元素到容器里
? ? mutating func append(_ item: ItemType)
? ? // 獲取容器中元素的數(shù)
? ? var count: Int { get }
? ? // 通過索引值類型為 Int 的下標(biāo)檢索到容器中的每一個元素
? ? subscript(i: Int) -> ItemType { get }
}
?
// // 遵循Container協(xié)議的泛型TOS類型
struct Stack<Element>: Container {
? ? // Stack<Element> 的原始實現(xiàn)部分
? ? var items = [Element]()
? ? mutating func push(_ item: Element) {
? ? ? ? items.append(item)
? ? }
? ? mutating func pop() -> Element {
? ? ? ? return items.removeLast()
? ? }
? ? // Container 協(xié)議的實現(xiàn)部分
? ? mutating func append(_ item: Element) {
? ? ? ? self.push(item)
? ? }
? ? var count: Int {
? ? ? ? return items.count
? ? }
? ? subscript(i: Int) -> Element {
? ? ? ? return items[i]
? ? }
}
// 擴(kuò)展,將 Array 當(dāng)作 Container 來使用
extension Array: Container {}
?
func allItemsMatch<C1: Container, C2: Container>
? ? (_ someContainer: C1, _ anotherContainer: C2) -> Bool
? ? where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
? ? ? ??
? ? ? ? // 檢查兩個容器含有相同數(shù)量的元素
? ? ? ? if someContainer.count != anotherContainer.count {
? ? ? ? ? ? return false
? ? ? ? }
? ? ? ??
? ? ? ? // 檢查每一對元素是否相等
? ? ? ? for i in 0..<someContainer.count {
? ? ? ? ? ? if someContainer[i] != anotherContainer[i] {
? ? ? ? ? ? ? ? return false
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??
? ? ? ? // 所有元素都匹配,返回 true
? ? ? ? return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
?
var aos = ["google", "runoob", "taobao"]
?
if allItemsMatch(tos, aos) {
? ? print("匹配所有元素")
} else {
? ? print("元素不匹配")
}
以上程序執(zhí)行輸出結(jié)果為:
匹配所有元素
Swift 訪問控制
訪問控制可以限定其他源文件或模塊中代碼對你代碼的訪問級別。
你可以明確地給單個類型(類、結(jié)構(gòu)體、枚舉)設(shè)置訪問級別,也可以給這些類型的屬性、函數(shù)、初始化方法、基本類型、下標(biāo)索引等設(shè)置訪問級別。
協(xié)議也可以被限定在一定的范圍內(nèi)使用,包括協(xié)議里的全局常量、變量和函數(shù)。
訪問控制基于模塊與源文件。
模塊指的是以獨立單元構(gòu)建和發(fā)布的 Framework 或 Application。在 Swift 中的一個模塊可以使用 import 關(guān)鍵字引入另外一個模塊。
源文件是單個源碼文件,它通常屬于一個模塊, 源文件可以包含多個類和函數(shù) 的定義。
Swift 為代碼中的實體提供了四種不同的訪問級別:public、internal、fileprivate、private。
訪問級別 | 定義 |
---|---|
public | 可以訪問自己模塊中源文件里的任何實體,別人也可以通過引入該模塊來訪問源文件里的所有實體。 |
internal | 可以訪問自己模塊中源文件里的任何實體,但是別人不能訪問該模塊中源文件里的實體。 |
fileprivate | 文件內(nèi)私有,只能在當(dāng)前源文件中使用。 |
private | 只能在類中訪問,離開了這個類或者結(jié)構(gòu)體的作用域外面就無法訪問。 |
public 為最高級訪問級別,private 為最低級訪問級別。
實例
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
?
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
除非有特殊的說明,否則實體都使用默認(rèn)的訪問級別 internal。
未指定訪問級別默認(rèn)為 internal
class SomeInternalClass {} ? ? ? ? ? ? ?// 訪問級別為 internal
let someInternalConstant = 0 ? ? ? ? ? ?// 訪問級別為 internal
函數(shù)類型訪問權(quán)限
函數(shù)的訪問級別需要根據(jù)該函數(shù)的參數(shù)類型和返回類型的訪問級別得出。
下面的例子定義了一個名為someFunction全局函數(shù),并且沒有明確地申明其訪問級別。
func someFunction() -> (SomeInternalClass, SomePrivateClass) {// 函數(shù)實現(xiàn)
}
函數(shù)中其中一個類 SomeInternalClass 的訪問級別是 internal,另一個 SomePrivateClass 的訪問級別是 private。所以根據(jù)元組訪問級別的原則,該元組的訪問級別是 private(元組的訪問級別與元組中訪問級別最低的類型一致)。
因為該函數(shù)返回類型的訪問級別是 private,所以你必須使用 private 修飾符,明確的聲明該函數(shù):
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {// 函數(shù)實現(xiàn)
}
將該函數(shù)申明為 public 或 internal,或者使用默認(rèn)的訪問級別 internal 都是錯誤的,因為如果這樣你就無法訪問 private 級別的返回值。
枚舉類型訪問權(quán)限
枚舉中成員的訪問級別繼承自該枚舉,你不能為枚舉中的成員單獨申明不同的訪問級別。
實例
比如下面的例子,枚舉 Student 被明確的申明為 public 級別,那么它的成員 Name,Mark 的訪問級別同樣也是 public:
實例
public enum Student {
? ? case Name(String)
? ? case Mark(Int,Int,Int)
}
?
var studDetails = Student.Name("Swift")
var studMarks = Student.Mark(98,97,95)
?
switch studMarks {
case .Name(let studName):
? ? print("學(xué)生名: \(studName).")
case .Mark(let Mark1, let Mark2, let Mark3):
? ? print("學(xué)生成績: \(Mark1),\(Mark2),\(Mark3)")
}
以上程序執(zhí)行輸出結(jié)果為:
學(xué)生成績: 98,97,95
子類訪問權(quán)限
子類的訪問級別不得高于父類的訪問級別。比如說,父類的訪問級別是 internal,子類的訪問級別就不能申明為 public。
實例
public class SuperClass {
? ? fileprivate func show() {
? ? ? ? print("超類")
? ? }
}
?
// 訪問級別不能高于超類 public > internal
internal class SubClass: SuperClass ?{
? ? override internal func show() {
? ? ? ? print("子類")
? ? }
}
?
let sup = SuperClass()
sup.show()
?
let sub = SubClass()
sub.show()
以上程序執(zhí)行輸出結(jié)果為:
超類 子類
常量、變量、屬性、下標(biāo)訪問權(quán)限
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。
比如說,你定義一個public級別的屬性,但是它的類型是private級別的,這是編譯器所不允許的。
同樣,下標(biāo)也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標(biāo)索引的定義類型是private級別的,那么它們必須要明確的申明訪問級別為private:
private var privateInstance = SomePrivateClass()
Getter 和 Setter訪問權(quán)限
常量、變量、屬性、下標(biāo)索引的Getters和Setters的訪問級別繼承自它們所屬成員的訪問級別。
Setter的訪問級別可以低于對應(yīng)的Getter的訪問級別,這樣就可以控制變量、屬性或下標(biāo)索引的讀寫權(quán)限。
實例
class Samplepgm {
? ? fileprivate var counter: Int = 0{
? ? ? ? willSet(newTotal){
? ? ? ? ? ? print("計數(shù)器: \(newTotal)")
? ? ? ? }
? ? ? ? didSet{
? ? ? ? ? ? if counter > oldValue {
? ? ? ? ? ? ? ? print("新增加數(shù)量 \(counter - oldValue)")
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
?
let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800
counter 的訪問級別為 fileprivate,在文件內(nèi)可以訪問。
以上程序執(zhí)行輸出結(jié)果為:
計數(shù)器: 100 新增加數(shù)量 100 計數(shù)器: 800 新增加數(shù)量 700
構(gòu)造器和默認(rèn)構(gòu)造器訪問權(quán)限
初始化
我們可以給自定義的初始化方法申明訪問級別,但是要不高于它所屬類的訪問級別。但必要構(gòu)造器例外,它的訪問級別必須和所屬類的訪問級別相同。
如同函數(shù)或方法參數(shù),初始化方法參數(shù)的訪問級別也不能低于初始化方法的訪問級別。
默認(rèn)初始化方法
Swift為結(jié)構(gòu)體、類都提供了一個默認(rèn)的無參初始化方法,用于給它們的所有屬性提供賦值操作,但不會給出具體值。
默認(rèn)初始化方法的訪問級別與所屬類型的訪問級別相同。
實例
在每個子類的 init() 方法前使用 required 關(guān)鍵字聲明訪問權(quán)限。
實例
class classA {
? ? required init() {
? ? ? ? var a = 10
? ? ? ? print(a)
? ? }
}
?
class classB: classA {
? ? required init() {
? ? ? ? var b = 30
? ? ? ? print(b)
? ? }
}
?
let res = classA()
let show = classB()
以上程序執(zhí)行輸出結(jié)果為:
10 30 10
協(xié)議訪問權(quán)限
如果想為一個協(xié)議明確的申明訪問級別,那么需要注意一點,就是你要確保該協(xié)議只在你申明的訪問級別作用域中使用。
如果你定義了一個public訪問級別的協(xié)議,那么實現(xiàn)該協(xié)議提供的必要函數(shù)也會是public的訪問級別。這一點不同于其他類型,比如,public訪問級別的其他類型,他們成員的訪問級別為internal。
實例
public protocol TcpProtocol {
? ? init(no1: Int)
}
?
public class MainClass {
? ? var no1: Int // local storage
? ? init(no1: Int) {
? ? ? ? self.no1 = no1 // initialization
? ? }
}
?
class SubClass: MainClass, TcpProtocol {
? ? var no2: Int
? ? init(no1: Int, no2 : Int) {
? ? ? ? self.no2 = no2
? ? ? ? super.init(no1:no1)
? ? }
? ??
? ? // Requires only one parameter for convenient method
? ? required override convenience init(no1: Int) ?{
? ? ? ? self.init(no1:no1, no2:0)
? ? }
}
?
let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)
?
print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")
以上程序執(zhí)行輸出結(jié)果為:
res is: 20 res is: 30 res is: 50
擴(kuò)展訪問權(quán)限
你可以在條件允許的情況下對類、結(jié)構(gòu)體、枚舉進(jìn)行擴(kuò)展。擴(kuò)展成員應(yīng)該具有和原始類成員一致的訪問級別。比如你擴(kuò)展了一個公共類型,那么你新加的成員應(yīng)該具有和原始成員一樣的默認(rèn)的internal訪問級別。
或者,你可以明確申明擴(kuò)展的訪問級別(比如使用private extension)給該擴(kuò)展內(nèi)所有成員申明一個新的默認(rèn)訪問級別。這個新的默認(rèn)訪問級別仍然可以被單獨成員所申明的訪問級別所覆蓋。
泛型訪問權(quán)限
泛型類型或泛型函數(shù)的訪問級別取泛型類型、函數(shù)本身、泛型類型參數(shù)三者中的最低訪問級別。
實例
public struct TOS<T> {
? ? var items = [T]()
? ? private mutating func push(item: T) {
? ? ? ? items.append(item)
? ? }
? ??
? ? mutating func pop() -> T {
? ? ? ? return items.removeLast()
? ? }
}
?
var tos = TOS<String>()
tos.push("Swift")
print(tos.items)
?
tos.push("泛型")
print(tos.items)
?
tos.push("類型參數(shù)")
print(tos.items)
?
tos.push("類型參數(shù)名")
print(tos.items)
let deletetos = tos.pop()
以上程序執(zhí)行輸出結(jié)果為:
["Swift"] ["Swift", "泛型"] ["Swift", "泛型", "類型參數(shù)"] ["Swift", "泛型", "類型參數(shù)", "類型參數(shù)名"]
類型別名
任何你定義的類型別名都會被當(dāng)作不同的類型,以便于進(jìn)行訪問控制。一個類型別名的訪問級別不可高于原類型的訪問級別。
比如說,一個private級別的類型別名可以設(shè)定給一個public、internal、private的類型,但是一個public級別的類型別名只能設(shè)定給一個public級別的類型,不能設(shè)定給internal或private 級別的類型。
注意:這條規(guī)則也適用于為滿足協(xié)議一致性而給相關(guān)類型命名別名的情況。
實例
public protocol Container {
? ? typealias ItemType
? ? mutating func append(item: ItemType)
? ? var count: Int { get }
? ? subscript(i: Int) -> ItemType { get }
}
?
struct Stack<T>: Container {
? ? // original Stack<T> implementation
? ? var items = [T]()
? ? mutating func push(item: T) {
? ? ? ? items.append(item)
? ? }
? ??
? ? mutating func pop() -> T {
? ? ? ? return items.removeLast()
? ? }
? ??
? ? // conformance to the Container protocol
? ? mutating func append(item: T) {
? ? ? ? self.push(item)
? ? }
? ??
? ? var count: Int {
? ? ? ? return items.count
? ? }
? ??
? ? subscript(i: Int) -> T {
? ? ? ? return items[i]
? ? }
}
?
func allItemsMatch<
? ? C1: Container, C2: Container
? ? where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
? ? (someContainer: C1, anotherContainer: C2) -> Bool {
? ? ? ? // check that both containers contain the same number of items
? ? ? ? if someContainer.count != anotherContainer.count {
? ? ? ? ? ? return false
? ? ? ? }
? ? ? ??
? ? ? ? // check each pair of items to see if they are equivalent
? ? ? ? for i in 0..<someContainer.count {
? ? ? ? ? ? if someContainer[i] != anotherContainer[i] {
? ? ? ? ? ? ? ? return false
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??
? ? ? ? // all items match, so return true
? ? ? ? return true
}
?
var tos = Stack<String>()
tos.push("Swift")
print(tos.items)
?
tos.push("泛型")
print(tos.items)
?
tos.push("Where 語句")
print(tos.items)
?
var eos = ["Swift", "泛型", "Where 語句"]
print(eos)
以上程序執(zhí)行輸出結(jié)果為:
["Swift"] ["Swift", "泛型"] ["Swift", "泛型", "Where 語句"] ["Swift", "泛型", "Where 語句"]