1唐瀑、隱式解析可選類(lèi)型
有時(shí)候在程序架構(gòu)中群凶,第一次被賦值之后,可以確定一個(gè)可選類(lèi)型總會(huì)有值介褥,這時(shí)候每次使用時(shí)都判斷和解析可選值是非常低效的座掘,因?yàn)榭梢源_定它總會(huì)有值。
把想要用作可選的類(lèi)型的后面的問(wèn)號(hào)(String?)改成感嘆號(hào)(String!)來(lái)聲明一個(gè)隱式解析可選類(lèi)型柔滔。
一個(gè)隱式解析可選類(lèi)型就是一個(gè)普通的可選類(lèi)型溢陪,但是可以當(dāng)做非可選類(lèi)型使用,不需要每次都是用解析來(lái)獲取可選值睛廊。
如下例子展示了可選類(lèi)型String和隱式解析可選類(lèi)型String的區(qū)別:
let possibleString: String? = "An optional string."
let forcedString: String! = possibleString! // 需要感嘆號(hào)來(lái)獲取值
let assumedString: String! = "An implicity unwrapped optional string."
let implicitString: String = assumedString // 不需要感嘆號(hào)
可以把隱式解析可選類(lèi)型當(dāng)做普通可選類(lèi)型來(lái)判斷它是否包含值:
if assumedString != nil {
print(assumedString!) // 輸出:"An implicity unwrapped optional string."
}
也可以在可選綁定中使用隱式解析可選類(lèi)型來(lái)檢查并解析它的值:
if let definiteString = assumedString {
print(definiteString) // 輸出:"An implicity unwrapped optional string."
}
2形真、輸入輸出參數(shù)
定義一個(gè)輸入輸出參數(shù)時(shí),在參數(shù)定義前加 inout 關(guān)鍵字超全。
一個(gè)輸入輸出參數(shù)有傳入函數(shù)的值咆霜,這個(gè)值被函數(shù)修改,然后被傳出函數(shù)嘶朱,替換原來(lái)的值蛾坯。
當(dāng)傳入的參數(shù)作為輸入輸出參數(shù)時(shí),需要在參數(shù)名前加 & 符疏遏,表示這個(gè)值可以被函數(shù)修改脉课。
注意:輸入輸出參數(shù)不能有默認(rèn)值,而且可變參數(shù)不能用 inout 標(biāo)記
例
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tempA = a
a = b
b = tempA
}
var someInt = 3, anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
3财异、閉包表達(dá)式語(yǔ)法的一般形式
{ (parameters) -> return type in
statements
}
4倘零、尾隨閉包
如果需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),將這個(gè)閉包替換為尾隨閉包的形式很有用戳寸。尾隨閉包是一個(gè)書(shū)寫(xiě)在函數(shù)圓括號(hào)之后的閉包表達(dá)式呈驶,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。使用尾隨閉包時(shí)疫鹊,不用寫(xiě)出它的參數(shù)標(biāo)簽:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
如果閉包表達(dá)式是函數(shù)或方法的唯一參數(shù)袖瞻,當(dāng)使用尾隨閉包時(shí),可以把 () 省略:
reversedNames = names.sorted { $0 > $1 }
5拆吆、逃逸閉包
當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中虏辫,且這個(gè)碧波啊在函數(shù)返回之后才被執(zhí)行,稱(chēng)該閉包從函數(shù)中逃逸锈拨。
當(dāng)定義接收閉包作為參數(shù)的函數(shù)時(shí)砌庄,需在參數(shù)名執(zhí)勤啊標(biāo)注 @escaping,用來(lái)指明這個(gè)閉包是允許“逃逸”出這個(gè)函數(shù)的奕枢。
例
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:)函數(shù)接受一個(gè)閉包作為參數(shù)娄昆,該閉包被添加到一個(gè)函數(shù)外定義的數(shù)組中。如果不將這個(gè)參數(shù)標(biāo)記為 @escaping缝彬,就會(huì)得到一個(gè)編譯錯(cuò)誤萌焰。
6、枚舉
枚舉的語(yǔ)法
enum SomeEnumeration {
// 枚舉定義放在這里
}
// 例
enum CompassPoint {
case north, south
case east
case west
}
與C和Objective-C不同谷浅,Swift的枚舉成員在被創(chuàng)建時(shí)不會(huì)被賦予一個(gè)默認(rèn)的整型值扒俯。
上面的CompassPoint例中奶卓,north、south撼玄、east夺姑、west不會(huì)被隱式地賦值為0,1掌猛,2盏浙,3。這些枚舉成員本身就是完備的值荔茬,這些值的類(lèi)型是CompassPoint類(lèi)型废膘。
7、枚舉成員的遍歷
令枚舉遵循 CaseIterable 協(xié)議慕蔚,Swift會(huì)生成一個(gè) allCases 屬性丐黄,表示一個(gè)包含枚舉所有成員的集合。例
enunm Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 打印“3 beverages available”
8孔飒、枚舉關(guān)聯(lián)值
可以為枚舉的一個(gè)成員(如Beverage.tea)設(shè)置一個(gè)常量或者變量孵稽,并在賦值之后查看這個(gè)值,這個(gè)額外的信息成為關(guān)聯(lián)值十偶。
每次使用枚舉成員時(shí)菩鲜,可以修改這個(gè)關(guān)聯(lián)值。
如果需要的話惦积,每個(gè)枚舉成員的關(guān)聯(lián)值類(lèi)型可以更不相同接校。
例
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
以上代碼可以這么理解:
定義一個(gè)名為 Barcode 的枚舉類(lèi)型,它的一個(gè)成員值是具有 (Int, Int, Int, Int) 類(lèi)型關(guān)聯(lián)值的 upc狮崩,另一個(gè)成員值是具有 String 類(lèi)型關(guān)聯(lián)值的 qrCode蛛勉。
這個(gè)定義不提供任何 Int 或 String 類(lèi)型的關(guān)聯(lián)值,只是定義了當(dāng) Barcode 常量和變量等于 Barcode.upc 或 Barcode.qrCode 時(shí)睦柴,可以存儲(chǔ)的關(guān)聯(lián)值的類(lèi)型诽凌。
然后可以使用任意一種條形碼類(lèi)型創(chuàng)建新的條形碼,如
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
上例創(chuàng)建了一個(gè)名為 productBarcode 的變量坦敌,并將 Barcode.upc 賦值給它侣诵,關(guān)聯(lián)的元組值為 (8, 85909, 51226, 3)。
枚舉關(guān)聯(lián)值與Switch結(jié)合使用:
可以在 switch 的 case 分支代碼中提取每個(gè)關(guān)聯(lián)值作為一個(gè)常量(用 let 前綴)或者作為一個(gè)變量(用 var 前綴)來(lái)使用:
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
如果一個(gè)枚舉成員的所有關(guān)聯(lián)值都被提取為常量狱窘,或者都被提取為變量杜顺,為了簡(jiǎn)潔,你可以只在成員名稱(chēng)前標(biāo)注一個(gè) let 或者 var:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
9蘸炸、枚舉原始值
作為關(guān)聯(lián)值的替代選擇躬络,枚舉成員可以被默認(rèn)值(成為原始值)預(yù)填充,原始值類(lèi)型必須相同搭儒。
原始值可以是字符串穷当、字符提茁,或者任意整型值或浮點(diǎn)型值。每個(gè)原始值在枚舉聲明中必須是唯一的馁菜。
例
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
注意
原始值和關(guān)聯(lián)值是不同的茴扁。原始值是在定義枚舉時(shí)被預(yù)先填充的值,像上述三個(gè) ASCII 碼火邓。對(duì)于一個(gè)特定的枚舉成員丹弱,它的原始值始終不變德撬。關(guān)聯(lián)值是創(chuàng)建一個(gè)基于枚舉成員的常量或變量時(shí)才設(shè)置的值铲咨,枚舉成員的關(guān)聯(lián)值可以變化。
枚舉原始值的隱式賦值
在使用原始值為整數(shù)或者字符串類(lèi)型的枚舉時(shí)蜓洪,不需要顯式地為每一個(gè)枚舉成員設(shè)置原始值纤勒,Swift 將會(huì)自動(dòng)為你賦值。
例如隆檀,當(dāng)使用整數(shù)作為原始值時(shí)摇天,隱式賦值的值依次遞增 1。如果第一個(gè)枚舉成員沒(méi)有設(shè)置原始值恐仑,其原始值將為 0泉坐。
當(dāng)使用字符串作為枚舉類(lèi)型的原始值時(shí),每個(gè)枚舉成員的隱式原始值為該枚舉成員的名稱(chēng)歪沃。
使用枚舉成員的 rawValue 屬性可以訪問(wèn)該枚舉成員的原始值喇喉。
枚舉原始值初始化枚舉實(shí)例
如果在定義枚舉類(lèi)型的時(shí)候使用了原始值挪拟,那么將會(huì)自動(dòng)獲得一個(gè)初始化方法,這個(gè)方法接收一個(gè)叫做 rawValue 的參數(shù)纯丸,參數(shù)類(lèi)型即為原始值類(lèi)型,返回值則是枚舉成員或 nil静袖。你可以使用這個(gè)初始化方法來(lái)創(chuàng)建一個(gè)新的枚舉實(shí)例觉鼻。
例,利用整型原始值表示每個(gè)行星在太陽(yáng)系中的順序:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// Plant.mercury 的顯式原始值為 1队橙,Planet.venus 的隱式原始值為 2坠陈,依次類(lèi)推。
這個(gè)列子使用原始值7創(chuàng)建了枚舉成員 Uranus:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 類(lèi)型為 Planet? 值為 Planet.uranus
注意
原始值構(gòu)造器是一個(gè)可失敗構(gòu)造器捐康,因?yàn)椴⒉皇敲恳粋€(gè)原始值都有與之對(duì)應(yīng)的枚舉成員畅姊。
10、遞歸枚舉
遞歸枚舉是一種枚舉類(lèi)型吹由,它有一個(gè)或多個(gè)枚舉成員使用該枚舉類(lèi)型的實(shí)例作為關(guān)聯(lián)值若未。使用遞歸枚舉時(shí),編譯器會(huì)插入一個(gè)間接層倾鲫。你可以在枚舉成員前加上 indirect 來(lái)表示該成員可遞歸粗合,也可以在枚舉類(lèi)型開(kāi)頭加上 indirect 關(guān)鍵字來(lái)表明它的所有成員都是可遞歸的萍嬉。
11、結(jié)構(gòu)體和類(lèi)
結(jié)構(gòu)體和類(lèi)的共同點(diǎn)
定義屬性用于存儲(chǔ)值
定義方法用于提供功能
定義下標(biāo)操作用于通過(guò)下標(biāo)語(yǔ)法訪問(wèn)它們的值
定義構(gòu)造器用于設(shè)置初始值
通過(guò)擴(kuò)展以增加默認(rèn)實(shí)現(xiàn)之外的功能
遵循協(xié)議以提供某種標(biāo)準(zhǔn)功能
與結(jié)構(gòu)體相比隙疚,類(lèi)還有如下的附加功能:
繼承允許一個(gè)類(lèi)繼承另一個(gè)類(lèi)的特征
類(lèi)型轉(zhuǎn)換允許在運(yùn)行時(shí)檢查和解釋一個(gè)類(lèi)實(shí)例的類(lèi)型
析構(gòu)器允許一個(gè)類(lèi)實(shí)例釋放任何其所被分配的資源
引用計(jì)數(shù)允許對(duì)一個(gè)類(lèi)的多次引用
優(yōu)先使用結(jié)構(gòu)體壤追,因?yàn)樗菀桌斫狻?/h6>
結(jié)構(gòu)體和類(lèi)--類(lèi)型定義的語(yǔ)法
struct SomeStructure {
// 在這里定義結(jié)構(gòu)體
}
class SomeClass {
// 在這里定義類(lèi)
}
struct SomeStructure {
// 在這里定義結(jié)構(gòu)體
}
class SomeClass {
// 在這里定義類(lèi)
}
Swift 中請(qǐng)使用駝峰命名法,其中類(lèi)型首字母大寫(xiě)供屉,屬性和方法首字母小寫(xiě)行冰。
定義結(jié)構(gòu)體和定義類(lèi)的示例:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
結(jié)構(gòu)體類(lèi)型的成員逐一構(gòu)造器
結(jié)構(gòu)體有一個(gè)自動(dòng)生成的成員逐一構(gòu)造器,用于初始化新結(jié)構(gòu)體實(shí)例中成員的屬性伶丐。新實(shí)例中各個(gè)屬性的初始值可以通過(guò)屬性的名稱(chēng)傳遞到成員逐一構(gòu)造器之中:
let vga = Resolution(width: 640, height: 480)
類(lèi)實(shí)例沒(méi)有默認(rèn)的成員逐一構(gòu)造器悼做。
結(jié)構(gòu)體和枚舉是值類(lèi)型
值類(lèi)型:當(dāng)它被賦值給一個(gè)變量、常量或者被傳遞給一個(gè)函數(shù)的時(shí)候哗魂,其值會(huì)被拷貝肛走。
Swift 中所有的基本類(lèi)型:整數(shù)(integer)、浮點(diǎn)數(shù)(floating-point number)录别、布爾值(boolean)朽色、字符串(string)、數(shù)組(array)和字典(dictionary)组题,都是值類(lèi)型葫男,其底層也是使用結(jié)構(gòu)體實(shí)現(xiàn)的。
Swift 中所有的結(jié)構(gòu)體和枚舉類(lèi)型都是值類(lèi)型崔列,它們的實(shí)例梢褐,以及實(shí)例中所包含的任何值類(lèi)型的屬性,在代碼中傳遞的時(shí)候都會(huì)被復(fù)制峻呕。
注意
標(biāo)準(zhǔn)庫(kù)定義的集合利职,例如數(shù)組,字典和字符串瘦癌,都對(duì)復(fù)制進(jìn)行了優(yōu)化以降低性能成本。新集合不會(huì)立即復(fù)制桶癣,而是跟原集合共享同一份內(nèi)存惹挟,共享同樣的元素。在集合的某個(gè)副本要被修改前,才會(huì)復(fù)制它的元素踱启。而你在代碼中看起來(lái)就像是立即發(fā)生了復(fù)制抖剿。
類(lèi)是引用類(lèi)型
與值類(lèi)型不同甥温,引用類(lèi)型在被賦予到一個(gè)變量乃秀、常量或者被傳遞到一個(gè)函數(shù)時(shí),其值不會(huì)被拷貝。因此缺亮,使用的是已存在實(shí)例的引用庄蹋,而不是其拷貝瞬内。
恒等運(yùn)算符
相同(===)
不相同(!==)
可以使用這兩個(gè)運(yùn)算符檢測(cè)兩個(gè)常量或者變量是否引用了同一個(gè)實(shí)例。
注意限书,“相同”(用三個(gè)等號(hào)表示虫蝶,===)與“等于”(用兩個(gè)等號(hào)表示,==)的不同倦西∧苷妫“相同”表示兩個(gè)類(lèi)類(lèi)型(class type)的常量或者變量引用同一個(gè)類(lèi)實(shí)例。“等于”表示兩個(gè)實(shí)例的值“相等”或“等價(jià)”粉铐,判定時(shí)要遵照設(shè)計(jì)者定義的評(píng)判標(biāo)準(zhǔn)疼约。
Choosing Between Structures and Classes(在結(jié)構(gòu)和類(lèi)之間進(jìn)行選擇)
考慮結(jié)構(gòu)體是值類(lèi)型,類(lèi)是引用類(lèi)型蝙泼。
12程剥、屬性
屬性將值與特定的類(lèi)、結(jié)構(gòu)體或枚舉關(guān)聯(lián)汤踏。
存儲(chǔ)屬性會(huì)將常量和變量存儲(chǔ)為實(shí)例的一部分织鲸,而計(jì)算屬性則是直接計(jì)算(而不是存儲(chǔ))值。
計(jì)算屬性可以用于類(lèi)溪胶、結(jié)構(gòu)體和枚舉搂擦,而存儲(chǔ)屬性只能用于類(lèi)和結(jié)構(gòu)體。
屬性也可以直接與類(lèi)型本身關(guān)聯(lián)哗脖,這種屬性稱(chēng)為類(lèi)型屬性瀑踢。
存儲(chǔ)屬性
存儲(chǔ)屬性就是存儲(chǔ)在特定類(lèi)或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var 定義)才避,也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let 定義)橱夭。
可以在定義存儲(chǔ)屬性的時(shí)候指定默認(rèn)值,請(qǐng)參考 默認(rèn)構(gòu)造器 工扎。也可以在構(gòu)造過(guò)程中設(shè)置或修改存儲(chǔ)屬性的值徘钥,甚至修改常量存儲(chǔ)屬性的值衔蹲,請(qǐng)參考 構(gòu)造過(guò)程中常量屬性的修改 肢娘。
常量結(jié)構(gòu)體實(shí)例的存儲(chǔ)屬性
如果創(chuàng)建了一個(gè)結(jié)構(gòu)體實(shí)例并將其賦值給一個(gè)常量,則無(wú)法修改該實(shí)例的任何屬性舆驶,即使被聲明為可變屬性也不行橱健。
因?yàn)榻Y(jié)構(gòu)體屬于值類(lèi)型。當(dāng)值類(lèi)型的實(shí)例被聲明為常量的時(shí)候沙廉,它的所有屬性也就成了常量拘荡。
屬于引用類(lèi)型的類(lèi)則不一樣。把一個(gè)引用類(lèi)型的實(shí)例賦給一個(gè)常量后撬陵,依然可以修改該實(shí)例的可變屬性珊皿。
延時(shí)加載存儲(chǔ)屬性
在屬性聲明前使用 lazy 來(lái)標(biāo)示一個(gè)延時(shí)加載存儲(chǔ)屬性。
注意
必須將延時(shí)加載屬性聲明成變量(使用 var 關(guān)鍵字)巨税,因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到蟋定。而常量屬性在構(gòu)造過(guò)程完成之前必須要有初始值,因此無(wú)法聲明成延時(shí)加載草添。
計(jì)算屬性
類(lèi)驶兜、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè) getter 和一個(gè)可選的 setter抄淑,來(lái)間接獲取和設(shè)置其他屬性或變量的值屠凶。
例
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)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”
簡(jiǎn)化 Setter 聲明
如果計(jì)算屬性的 setter 沒(méi)有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱(chēng) 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)
}
}
}
簡(jiǎn)化 Getter 聲明
如果整個(gè) getter 是單一表達(dá)式矗愧,getter 會(huì)隱式地返回這個(gè)表達(dá)式結(jié)果。
例
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
在 getter 中忽略 return
與在函數(shù)中忽略 return
的規(guī)則相同郑原,參考 隱式返回的函數(shù)贱枣。
只讀計(jì)算屬性
只有 getter 沒(méi)有 setter 的計(jì)算屬性叫只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值颤专,可以通過(guò)點(diǎn)運(yùn)算符訪問(wèn)纽哥,但不能設(shè)置新的值。
注意
必須使用 var 關(guān)鍵字定義計(jì)算屬性栖秕,包括只讀計(jì)算屬性春塌,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹et 關(guān)鍵字只用來(lái)聲明常量屬性簇捍,表示初始化后再也無(wú)法修改的值只壳。
只讀計(jì)算屬性的聲明可以去掉 get 關(guān)鍵字和花括號(hào):
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印“the volume of fourByFiveByTwo is 40.0”
屬性觀察器
屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器暑塑,即使新值和當(dāng)前值相同的時(shí)候也不例外吼句。
可以在以下位置添加屬性觀察器:
- 自定義的存儲(chǔ)屬性
- 繼承的存儲(chǔ)屬性
- 繼承的計(jì)算屬性
對(duì)于繼承的屬性,你可以在子類(lèi)中通過(guò)重寫(xiě)屬性的方式為它添加屬性觀察器事格。對(duì)于自定義的計(jì)算屬性來(lái)說(shuō)惕艳,使用它的 setter 監(jiān)控和響應(yīng)值的變化。
可以為屬性添加其中一個(gè)或兩個(gè)觀察器: - willSet 在新的值被設(shè)置之前調(diào)用
- didSet 在新的值被設(shè)置之后調(diào)用
willSet 觀察期會(huì)將新的屬性值作為常量參數(shù)傳入驹愚,在 willSet 的實(shí)現(xiàn)代碼中可以為這個(gè)參數(shù)指定一個(gè)名稱(chēng)远搪,如果不指定則參數(shù)仍然可用,這時(shí)使用默認(rèn)名稱(chēng) newValue 表示逢捺。
同樣谁鳍,didSet 觀察器會(huì)將舊的屬性值作為參數(shù)傳入,可以為該參數(shù)指定一個(gè)名稱(chēng)或者使用默認(rèn)參數(shù)名 oldValue劫瞳。如果在 didSet 方法中再次對(duì)該屬性賦值倘潜,那么新值會(huì)覆蓋舊的值。
注意:在父類(lèi)初始化方法調(diào)用之后志于,在子類(lèi)構(gòu)造器中給父類(lèi)的屬性賦值時(shí)涮因,會(huì)調(diào)用父類(lèi)屬性的 willSet 和 didSet 觀察器。而在父類(lèi)初始化方法調(diào)用之前恨憎,給子類(lèi)的屬性賦值時(shí)不會(huì)調(diào)用子類(lèi)屬性的觀察器蕊退。
例
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("將 totalSteps 的值設(shè)置為 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 將 totalSteps 的值設(shè)置為 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 將 totalSteps 的值設(shè)置為 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 將 totalSteps 的值設(shè)置為 896
// 增加了 536 步
屬性包裝器
屬性包裝器這個(gè)暫時(shí)沒(méi)有理解好郊楣!
全局變量和局部變量
全局變量是在函數(shù)、方法瓤荔、閉包或任何類(lèi)型之外定義的變量净蚤。局部變量是在函數(shù)、方法或閉包內(nèi)部定義的變量输硝。
注意:全局的常量或變量都是延遲計(jì)算的今瀑,跟 延時(shí)加載存儲(chǔ)屬性相似,不同的地方在于点把,全局的常量或變量不需要標(biāo)記
lazy
修飾符橘荠。
局部范圍的常量和變量從不延遲計(jì)算。
類(lèi)型屬性
實(shí)例屬性屬于一個(gè)特定類(lèi)型的實(shí)例郎逃,每創(chuàng)建一個(gè)實(shí)例哥童,實(shí)例都擁有屬于自己的一套屬性值,實(shí)例之間的屬性相互獨(dú)立褒翰。
可以為類(lèi)型本身定義屬性贮懈,無(wú)論創(chuàng)建了多少個(gè)該類(lèi)型的實(shí)例,這些屬性都只有唯一一份优训。這種屬性就是類(lèi)型屬性朵你。
類(lèi)型屬性用于定義某個(gè)類(lèi)型所有實(shí)例共享的數(shù)據(jù)。
存儲(chǔ)型類(lèi)型屬性可以是變量或常量揣非,計(jì)算型類(lèi)型屬性跟實(shí)例的計(jì)算型屬性一樣只能定義成變量屬性抡医。
注意:跟實(shí)例的存儲(chǔ)型屬性不同,必須給存儲(chǔ)型類(lèi)型屬性指定默認(rèn)值早敬,因?yàn)轭?lèi)型本身沒(méi)有構(gòu)造器忌傻,也就無(wú)法在初始化過(guò)程中使用構(gòu)造器給類(lèi)型屬性賦值。
存儲(chǔ)型類(lèi)型屬性是延遲初始化的搁嗓,它們只有在第一次被訪問(wèn)的時(shí)候才會(huì)被初始化芯勘。即使它們被多個(gè)線程同時(shí)訪問(wèn),系統(tǒng)也保證只會(huì)對(duì)其進(jìn)行一次初始化腺逛,并且不需要對(duì)其使用 lazy 修飾符。
類(lèi)型屬性語(yǔ)法
在 C 或 Objective-C 中衡怀,與某個(gè)類(lèi)型關(guān)聯(lián)的靜態(tài)常量和靜態(tài)變量棍矛,是作為 global(全局)靜態(tài)變量定義的。
Swift 中抛杨,類(lèi)型屬性是作為類(lèi)型定義的一部分寫(xiě)在類(lèi)型最外層的花括號(hào)內(nèi)够委,因此它的作用范圍也就在類(lèi)型支持的范圍內(nèi)。
使用關(guān)鍵字 static 來(lái)定義類(lèi)型屬性怖现。在為類(lèi)定義計(jì)算型類(lèi)型屬性時(shí)茁帽,可以改用關(guān)鍵字 class 來(lái)支持子類(lèi)對(duì)父類(lèi)的實(shí)現(xiàn)進(jìn)行重寫(xiě)玉罐。
下面的例子演示了存儲(chǔ)型和計(jì)算型類(lèi)型屬性的語(yǔ)法:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
獲取和設(shè)置類(lèi)型屬性的值
跟實(shí)例屬性一樣,類(lèi)型屬性也是通過(guò)點(diǎn)運(yùn)算符來(lái)訪問(wèn)潘拨。但是吊输,類(lèi)型屬性是通過(guò)類(lèi)型本身來(lái)訪問(wèn),而不是通過(guò)實(shí)例铁追。比如:
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”
13季蚂、方法
方法是與某些特定類(lèi)型相關(guān)聯(lián)的函數(shù)。
實(shí)例方法
實(shí)例方法是屬于某個(gè)特定類(lèi)琅束、結(jié)構(gòu)體或者枚舉類(lèi)型實(shí)例的方法扭屁。實(shí)例方法提供訪問(wèn)和修改實(shí)例屬性的方法或提供與實(shí)例目的相關(guān)的功能,并以此來(lái)支撐實(shí)例的功能涩禀。實(shí)例方法的語(yǔ)法與函數(shù)完全一致料滥。
self屬性
類(lèi)型的每一個(gè)實(shí)例都有一個(gè)隱含屬性叫做 self,self 完全等同于該實(shí)例本身艾船。
不必在你的代碼里面經(jīng)常寫(xiě) self幔欧,只要在一個(gè)方法中使用一個(gè)已知的屬性或者方法名稱(chēng),如果你沒(méi)有明確地寫(xiě) self丽声,Swift 假定你是指當(dāng)前實(shí)例的屬性或者方法礁蔗。
使用這條規(guī)則的主要場(chǎng)景是實(shí)例方法的某個(gè)參數(shù)名稱(chēng)與實(shí)例的某個(gè)屬性名稱(chēng)相同的時(shí)候。在這種情況下雁社,參數(shù)名稱(chēng)享有優(yōu)先權(quán)浴井,并且在引用屬性時(shí)必須使用一種更嚴(yán)格的方式。這時(shí)你可以使用 self 屬性來(lái)區(qū)分參數(shù)名稱(chēng)和屬性名稱(chēng)霉撵。
在實(shí)例方法中修改值類(lèi)型
結(jié)構(gòu)體和枚舉是值類(lèi)型磺浙。默認(rèn)情況下,值類(lèi)型的屬性不能在它的實(shí)例方法中被修改徒坡。
如果你確實(shí)需要在某個(gè)特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性撕氧,你可以為這個(gè)方法選擇 可變(mutating)行為,然后就可以從其方法內(nèi)部改變它的屬性喇完;并且這個(gè)方法做的任何改變都會(huì)在方法執(zhí)行結(jié)束時(shí)寫(xiě)回到原始結(jié)構(gòu)中伦泥。
方法還可以給它隱含的 self 屬性賦予一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束時(shí)會(huì)替換現(xiàn)存實(shí)例锦溪。
要使用 可變方法不脯,將關(guān)鍵字 mutating 放到方法的 func 關(guān)鍵字之前就可以了:
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”
注意,不能在結(jié)構(gòu)體類(lèi)型的常量(a constant of structure type)上調(diào)用可變方法刻诊,因?yàn)槠鋵傩圆荒鼙桓淖兎揽词箤傩允亲兞繉傩浴?/p>
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 這里將會(huì)報(bào)告一個(gè)錯(cuò)誤
在可變方法中給 self 賦值
可變方法能夠賦給隱含屬性 self 一個(gè)全新的實(shí)例。上面 Point 的例子可以用下面的方式改寫(xiě):
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
新版的可變方法 moveBy(x:y:) 創(chuàng)建了一個(gè)新的結(jié)構(gòu)體實(shí)例则涯,它的 x 和 y 的值都被設(shè)定為目標(biāo)值复局。調(diào)用這個(gè)版本的方法和調(diào)用上個(gè)版本的最終結(jié)果是一樣的冲簿。
枚舉的可變方法(mutating)
枚舉的可變方法可以把 self 設(shè)置為同一枚舉類(lèi)型中不同的成員:
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight 現(xiàn)在等于 .high
ovenLight.next()
// ovenLight 現(xiàn)在等于 .off
類(lèi)型方法
在類(lèi)型本身上調(diào)用的方法,這種方法就叫做類(lèi)型方法亿昏。
在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 static峦剔,來(lái)指定類(lèi)型方法。
類(lèi)還可以用關(guān)鍵字 class 來(lái)指定龙优,從而允許子類(lèi)重寫(xiě)父類(lèi)該方法的實(shí)現(xiàn)羊异。
注意:在 Swift 中,你可以為所有的類(lèi)彤断、結(jié)構(gòu)體和枚舉定義類(lèi)型方法野舶。每一個(gè)類(lèi)型方法都被它所支持的類(lèi)型顯式包含。
類(lèi)型方法和實(shí)例方法一樣用點(diǎn)語(yǔ)法調(diào)用宰衙。但是平道,你是在類(lèi)型上調(diào)用這個(gè)方法,而不是在實(shí)例上調(diào)用供炼。
例
class SomeClass {
class func someTypeMethod() {
// 在這里實(shí)現(xiàn)類(lèi)型方法
}
}
SomeClass.someTypeMethod()
在類(lèi)型方法的方法體(body)中一屋,self 屬性指向這個(gè)類(lèi)型本身,而不是類(lèi)型的某個(gè)實(shí)例袋哼。
這意味著你可以用 self 來(lái)消除類(lèi)型屬性和類(lèi)型方法參數(shù)之間的歧義冀墨。
14、下標(biāo)
下標(biāo)可以定義在類(lèi)涛贯、結(jié)構(gòu)體和枚舉中诽嘉,是訪問(wèn)集合、列表或序列中元素的快捷方式弟翘。
可以使用下標(biāo)的索引虫腋,設(shè)置和獲取值,而不需要再調(diào)用對(duì)應(yīng)的存取方法稀余。
舉例來(lái)說(shuō)悦冀,用下標(biāo)訪問(wèn)一個(gè) Array 實(shí)例中的元素可以寫(xiě)作 someArray[index],訪問(wèn) Dictionary 實(shí)例中的元素可以寫(xiě)作 someDictionary[key]睛琳。
下標(biāo)語(yǔ)法
subscript(index: Int) -> Int {
get {
// 返回一個(gè)適當(dāng)?shù)?Int 類(lèi)型的值
}
set(newValue) {
// 執(zhí)行適當(dāng)?shù)馁x值操作
// newValue可以不指定盒蟆,setter 會(huì)提供一個(gè)名為 newValue 的默認(rèn)參數(shù)。
}
}
15掸掏、繼承
一個(gè)類(lèi)可以繼承另一個(gè)類(lèi)的方法茁影,屬性和其它特性。當(dāng)一個(gè)類(lèi)繼承其它類(lèi)時(shí)丧凤,繼承類(lèi)叫子類(lèi),被繼承類(lèi)叫超類(lèi)(或父類(lèi))步脓。在 Swift 中愿待,繼承是區(qū)分「類(lèi)」與其它類(lèi)型的一個(gè)基本特征浩螺。
在 Swift 中,類(lèi)可以調(diào)用和訪問(wèn)超類(lèi)的方法仍侥、屬性和下標(biāo)要出,并且可以重寫(xiě)這些方法,屬性和下標(biāo)來(lái)優(yōu)化或修改它們的行為农渊。
基類(lèi)
不繼承于其它類(lèi)的類(lèi)患蹂,稱(chēng)之為基類(lèi)。
重寫(xiě)
子類(lèi)可以為繼承來(lái)的實(shí)例方法砸紊,類(lèi)方法传于,實(shí)例屬性,類(lèi)屬性醉顽,或下標(biāo)提供自己定制的實(shí)現(xiàn)沼溜。這種行為叫重寫(xiě)。
如果要重寫(xiě)某個(gè)特性游添,你需要在重寫(xiě)定義的前面加上 override
關(guān)鍵字系草。
缺少 override
關(guān)鍵字的重寫(xiě)都會(huì)在編譯時(shí)被認(rèn)定為錯(cuò)誤。
防止重寫(xiě)
可以通過(guò)把方法唆涝,屬性或下標(biāo)標(biāo)記為 final 來(lái)防止它們被重寫(xiě)找都,只需要在聲明關(guān)鍵字前加上 final 修飾符即可(例如:final var、final func廊酣、final class func 以及 final subscript)能耻。
可以通過(guò)在關(guān)鍵字 class 前添加 final 修飾符(final class)來(lái)將整個(gè)類(lèi)標(biāo)記為 final 。
16啰扛、構(gòu)造過(guò)程
構(gòu)造過(guò)程是使用類(lèi)嚎京、結(jié)構(gòu)體或枚舉類(lèi)型的實(shí)例之前的準(zhǔn)備過(guò)程。在新實(shí)例使用前這個(gè)過(guò)程是必須的隐解,它包括設(shè)置實(shí)例中每個(gè)存儲(chǔ)屬性的初始值和執(zhí)行其他必須的設(shè)置或構(gòu)造過(guò)程鞍帝。
Swift 的構(gòu)造器沒(méi)有返回值。它們的主要任務(wù)是保證某種類(lèi)型的新實(shí)例在第一次使用前完成正確的初始化煞茫。
存儲(chǔ)屬性的初始賦值
類(lèi)和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí)帕涌,必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。存儲(chǔ)型屬性的值不能處于一個(gè)未知的狀態(tài)续徽。
可以在構(gòu)造器中為存儲(chǔ)型屬性設(shè)置初始值蚓曼,也可以在定義屬性時(shí)分配默認(rèn)值。
注意:當(dāng)你為存儲(chǔ)型屬性分配默認(rèn)值或者在構(gòu)造器中設(shè)置初始值時(shí)钦扭,它們的值是被直接設(shè)置的氓辣,不會(huì)觸發(fā)任何屬性觀察者隙畜。
構(gòu)造器的更多知識(shí)參見(jiàn)--->構(gòu)造過(guò)程
通過(guò)閉包或函數(shù)設(shè)置屬性的默認(rèn)值
如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要一些自定義或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值贼急。每當(dāng)某個(gè)屬性所在類(lèi)型的新實(shí)例被構(gòu)造時(shí),對(duì)應(yīng)的閉包或函數(shù)會(huì)被調(diào)用,而它們的返回值會(huì)當(dāng)做默認(rèn)值賦值給這個(gè)屬性。
例
class SomeClass {
let someProperty: SomeType = {
// 在這個(gè)閉包中給 someProperty 創(chuàng)建一個(gè)默認(rèn)值
// someValue 必須和 SomeType 類(lèi)型相同
return someValue
}()
}
我常用的
lazy var tableView: UITableView = {() -> UITableView in
let frame = CGRect(x: 0.0, y: swiftNavHeight, width: swiftScreenWidth, height: swiftScreenHeight - swiftNavHeight)
let tmpV: UITableView = UITableView(frame: frame, style: .plain)
tmpV.dataSource = self
tmpV.delegate = self
tmpV.estimatedRowHeight = CGFloat(TTJoinTeamInputCell.cellHeight)
return tmpV
}()
17、析構(gòu)過(guò)程
析構(gòu)器只適用于類(lèi)類(lèi)型痹雅,當(dāng)一個(gè)類(lèi)的實(shí)例被釋放之前,析構(gòu)器會(huì)被立即調(diào)用糊识。
析構(gòu)器用關(guān)鍵字 deinit 來(lái)標(biāo)示绩社,類(lèi)似于構(gòu)造器要用 init 來(lái)標(biāo)示。
可選鏈
可選鏈?zhǔn)秸{(diào)用是一種可以在當(dāng)前值可能為 nil 的可選值上請(qǐng)求和調(diào)用屬性赂苗、方法及下標(biāo)的方法愉耙。如果可選值有值,那么調(diào)用就會(huì)成功哑梳;如果可選值是 nil劲阎,那么調(diào)用將返回 nil。多個(gè)調(diào)用可以連接在一起形成一個(gè)調(diào)用鏈鸠真,如果其中任何一個(gè)節(jié)點(diǎn)為 nil悯仙,整個(gè)調(diào)用鏈都會(huì)失敗,即返回 nil吠卷。
注意:Swift 的可選鏈?zhǔn)秸{(diào)用和 Objective-C 中向 nil 發(fā)送消息有些相像锡垄,但是 Swift 的可選鏈?zhǔn)秸{(diào)用可以應(yīng)用于任意類(lèi)型,并且能檢查調(diào)用是否成功祭隔。
可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類(lèi)型货岭,但是被包裝成了一個(gè)可選值。
例如疾渴,使用可選鏈?zhǔn)秸{(diào)用訪問(wèn)屬性千贯,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時(shí),如果屬性原本的返回結(jié)果是 Int 類(lèi)型搞坝,則會(huì)變?yōu)?Int? 類(lèi)型搔谴。
通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用方法
可以通過(guò)可選鏈?zhǔn)钦{(diào)用來(lái)調(diào)用方法,并判斷是否調(diào)用成功桩撮,即使這個(gè)方法沒(méi)有返回值敦第。
沒(méi)有返回值的方法具有隱式的返回類(lèi)型Void,該值為一個(gè)空元祖店量,寫(xiě)成()芜果。
下面的例子,通過(guò)判斷返回值是否為nil融师,來(lái)判斷賦值是否成功:
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// 打印“It was not possible to set the address.”
通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)下標(biāo)
通過(guò)可選鏈?zhǔn)秸{(diào)用右钾,我們可以在一個(gè)可選值上訪問(wèn)下標(biāo),并且判斷下標(biāo)調(diào)用是否成功。
通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)可選值的下標(biāo)時(shí)霹粥,應(yīng)該將問(wèn)號(hào)放在下標(biāo)方括號(hào)的前面而不是后面灭将√勰瘢可選鏈?zhǔn)秸{(diào)用的問(wèn)號(hào)一般直接跟在可選表達(dá)式的后面后控。
例
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印“Unable to retrieve the first room name.”
18、錯(cuò)誤處理
錯(cuò)誤處理(Error handling) 是響應(yīng)錯(cuò)誤以及從錯(cuò)誤中恢復(fù)的過(guò)程空镜。Swift 在運(yùn)行時(shí)提供了拋出浩淘、捕獲、傳遞和操作可恢復(fù)錯(cuò)誤(recoverable errors)的一等支持(first-class support)吴攒。
注意:Swift 中的錯(cuò)誤處理涉及到錯(cuò)誤處理模式张抄,這會(huì)用到 Cocoa 和 Objective-C 中的 NSError。
表示與拋出錯(cuò)誤
Swift 中洼怔,錯(cuò)誤用遵循 Error 協(xié)議的類(lèi)型的值來(lái)表示署惯。這個(gè)空協(xié)議表明該類(lèi)型可以用于錯(cuò)誤處理。
Swift 的枚舉類(lèi)型尤為適合構(gòu)建一組相關(guān)的錯(cuò)誤狀態(tài)镣隶,枚舉的關(guān)聯(lián)值還可以提供錯(cuò)誤狀態(tài)的額外信息极谊。
例如,在游戲中操作自動(dòng)販賣(mài)機(jī)時(shí)安岂,你可以這樣表示可能會(huì)出現(xiàn)的錯(cuò)誤狀態(tài):
enum VendingMachineError: Error {
case invalidSelection // 選擇無(wú)效
case insufficientFunds(coinsNeeded: Int) // 金額不足
case outOfStock // 缺貨
}
拋出一個(gè)錯(cuò)誤可以讓你表明有意外情況發(fā)生轻猖,導(dǎo)致正常的執(zhí)行流程無(wú)法繼續(xù)執(zhí)行。拋出錯(cuò)誤使用 throw 語(yǔ)句域那。
例如咙边,下面的代碼拋出一個(gè)錯(cuò)誤,提示販賣(mài)機(jī)還需要 5 個(gè)硬幣:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
處理錯(cuò)誤
某個(gè)錯(cuò)誤被拋出時(shí)次员,附近的某部分代碼必須負(fù)責(zé)處理這個(gè)錯(cuò)誤败许。
Swift 中有 4 種處理錯(cuò)誤的方式:
1)把函數(shù)拋出的錯(cuò)誤傳遞給調(diào)用此函數(shù)的代碼
2)用 do-catch 語(yǔ)句處理錯(cuò)誤
3)將錯(cuò)誤作為可選類(lèi)型處理
4)或者斷言此錯(cuò)誤根本不會(huì)發(fā)生。
用 throwing 函數(shù)傳遞錯(cuò)誤
為了表示一個(gè)函數(shù)淑蔚、方法或構(gòu)造器可以拋出錯(cuò)誤市殷,在函數(shù)聲明的參數(shù)之后加上 throws 關(guān)鍵字。
一個(gè)標(biāo)有 throws 關(guān)鍵字的函數(shù)被稱(chēng)作 throwing 函數(shù)束倍。
如果這個(gè)函數(shù)指明了返回值類(lèi)型被丧,throws 關(guān)鍵詞需要寫(xiě)在返回箭頭(->)的前面。
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
一個(gè) throwing 函數(shù)可以在其內(nèi)部拋出錯(cuò)誤绪妹,并將錯(cuò)誤傳遞到函數(shù)被調(diào)用時(shí)的作用域甥桂。
在調(diào)用拋出錯(cuò)誤的方法的地方,必須要么直接處理這些錯(cuò)誤--使用 do-catch 語(yǔ)句邮旷,try? 或 try!黄选;要么繼續(xù)將這些錯(cuò)誤傳遞下去。
注意:只有 throwing 函數(shù)可以傳遞錯(cuò)誤,非 throwing 函數(shù)內(nèi)部拋出的錯(cuò)誤只能在函數(shù)內(nèi)部處理办陷。
用 Do-Catch 處理錯(cuò)誤
可以用一個(gè) do-catch 語(yǔ)句運(yùn)行一段閉包代碼來(lái)處理錯(cuò)誤貌夕。
如果在 do 子句中的代碼拋出了一個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤會(huì)與 catch 子句匹配民镜,從而決定哪條子句處理它啡专。
do-catch 語(yǔ)句的一般形式:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch pattern 3, pattern 4 where condition {
statements
} catch {
statements
}
在 catch 后面寫(xiě)一個(gè)匹配模式來(lái)表明這個(gè)子句能處理什么樣的錯(cuò)誤。
如果一條 catch 子句沒(méi)有指定匹配模式制圈,那么這條子句可以匹配任何錯(cuò)誤们童,并且把錯(cuò)誤綁定到一個(gè)名字為 error 的局部常量。
將錯(cuò)誤轉(zhuǎn)換成可選值
可以使用 try? 通過(guò)將錯(cuò)誤轉(zhuǎn)換成一個(gè)可選值來(lái)處理錯(cuò)誤鲸鹦。如果是在計(jì)算 try? 表達(dá)式時(shí)拋出錯(cuò)誤慧库,該表達(dá)式的結(jié)果就為 nil。
例馋嗜,在下面的代碼中齐板,x 和 y 有著相同的數(shù)值和等價(jià)的含義:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
如果 someThrowingFunction() 拋出一個(gè)錯(cuò)誤,x 和 y 的值是 nil葛菇。否則 x 和 y 的值就是該函數(shù)的返回值甘磨。
無(wú)論 someThrowingFunction() 的返回值類(lèi)型是什么類(lèi)型,x 和 y 都是這個(gè)類(lèi)型的可選類(lèi)型熟呛。
禁用錯(cuò)誤傳遞
有時(shí)你知道某個(gè) throwing 函數(shù)實(shí)際上在運(yùn)行時(shí)是不會(huì)拋出錯(cuò)誤的宽档,可以在表達(dá)式前面寫(xiě) try! 來(lái)禁用錯(cuò)誤傳遞,這會(huì)把調(diào)用包裝在一個(gè)不會(huì)有錯(cuò)誤拋出的運(yùn)行時(shí)斷言中庵朝。如果真的拋出了錯(cuò)誤吗冤,你會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。
例
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
指定清理操作
defer:可以使用 defer 語(yǔ)句在即將離開(kāi)當(dāng)前代碼塊時(shí)執(zhí)行一系列語(yǔ)句九府。
defer 語(yǔ)句將代碼的執(zhí)行延遲到當(dāng)前的作用域退出之前椎瘟。
該語(yǔ)句由 defer 關(guān)鍵字和要被延遲執(zhí)行的語(yǔ)句組成。
延遲執(zhí)行的語(yǔ)句不能包含任何控制轉(zhuǎn)移語(yǔ)句侄旬,例如 break肺蔚、return 語(yǔ)句,或是拋出一個(gè)錯(cuò)誤儡羔。延遲執(zhí)行的操作會(huì)按照它們聲明的順序從后往前執(zhí)行——也就是說(shuō)宣羊,第一條 defer 語(yǔ)句中的代碼最后才執(zhí)行,第二條 defer 語(yǔ)句中的代碼倒數(shù)第二個(gè)執(zhí)行汰蜘,以此類(lèi)推仇冯。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件
}
// close(file)會(huì)在這里被調(diào)用,即作用域的最后族操。
}
}
注意:即使沒(méi)有涉及到錯(cuò)誤處理的代碼苛坚,你也可以使用 defer 語(yǔ)句。
19、類(lèi)型轉(zhuǎn)換
類(lèi)型轉(zhuǎn)換可以判斷實(shí)例的類(lèi)型泼舱,也可以將實(shí)例看做是其父類(lèi)或者子類(lèi)的實(shí)例等缀。
類(lèi)型轉(zhuǎn)換在 Swift 中使用 is 和 as 操作符實(shí)現(xiàn)。這兩個(gè)操作符分別提供了一種簡(jiǎn)單達(dá)意的方式去檢查值的類(lèi)型或者轉(zhuǎn)換它的類(lèi)型娇昙。
檢查類(lèi)型
用類(lèi)型檢查操作符(is)來(lái)檢查一個(gè)實(shí)例是否屬于特定子類(lèi)型尺迂。若實(shí)例屬于那個(gè)子類(lèi)型,類(lèi)型檢查操作符返回 true涯贞,否則返回 false枪狂。
向下轉(zhuǎn)型
當(dāng)一個(gè)常量或變量可能在幕后屬于一個(gè)子類(lèi),可以嘗試用類(lèi)型轉(zhuǎn)換操作符(as? 或 as!)向下轉(zhuǎn)到它的子類(lèi)型宋渔。
向下轉(zhuǎn)型可能會(huì)失敗,類(lèi)型轉(zhuǎn)型操作符帶有兩種不同形式辜限。
條件形式 as? 返回一個(gè)你試圖向下轉(zhuǎn)成的類(lèi)型的可選值皇拣。
強(qiáng)制形式 as! 把試圖向下轉(zhuǎn)型和強(qiáng)制解包轉(zhuǎn)換結(jié)果結(jié)合為一個(gè)操作。
Any 和 AnyObject 的類(lèi)型轉(zhuǎn)換
Swift 為不確定類(lèi)型提供了兩種特殊的類(lèi)型別名:
- Any 可以表示任何類(lèi)型薄嫡,包括函數(shù)類(lèi)型氧急。
- AnyObject 可以表示任何類(lèi)類(lèi)型的實(shí)例。
注意:Any 類(lèi)型可以表示所有類(lèi)型的值毫深,包括可選類(lèi)型吩坝。Swift 會(huì)在你用 Any 類(lèi)型來(lái)表示一個(gè)可選值的時(shí)候,給你一個(gè)警告哑蔫。如果你確實(shí)想使用 Any 類(lèi)型來(lái)承載可選值钉寝,你可以使用 as 操作符顯式轉(zhuǎn)換為 Any,如下所示:
let optionalNumber: Int? = 3
things.append(optionalNumber) // 警告
things.append(optionalNumber as Any) // 沒(méi)有警告
20闸迷、嵌套類(lèi)型
要在一個(gè)類(lèi)型中嵌套另一個(gè)類(lèi)型嵌纲,將嵌套類(lèi)型的定義寫(xiě)在其外部類(lèi)型的 {} 內(nèi),而且可以根據(jù)需要定義多級(jí)嵌套腥沽。
嵌套類(lèi)型實(shí)踐
struct BlackjackCard {
// 嵌套的 Suit 枚舉
enum Suit: Character {
case spades = "?", hearts = "?", diamonds = "?", clubs = "?"
}
// 嵌套的 Rank 枚舉
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
// BlackjackCard 的屬性和方法
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
引用嵌套類(lèi)型
外部引用嵌套類(lèi)型時(shí)逮走,在嵌套類(lèi)型的類(lèi)型名前加上其外部類(lèi)型的類(lèi)型名作為前綴:
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
21、擴(kuò)展
擴(kuò)展可以給一個(gè)現(xiàn)有的類(lèi)今阳,結(jié)構(gòu)體师溅,枚舉盾舌,還有協(xié)議添加新的功能。
它還擁有不需要訪問(wèn)被擴(kuò)展類(lèi)型源代碼就能完成擴(kuò)展的能力(即逆向建模)榆综。
擴(kuò)展和 Objective-C 的分類(lèi)很相似挪哄。(與 Objective-C 分類(lèi)不同的是刻两,Swift 擴(kuò)展是沒(méi)有名字的增蹭。)
Swift 中的擴(kuò)展可以:
- 添加計(jì)算型實(shí)例屬性和計(jì)算型類(lèi)屬性
- 定義實(shí)例方法和類(lèi)方法
- 提供新的構(gòu)造器
- 定義下標(biāo)
- 定義和使用新的嵌套類(lèi)型
- 使已經(jīng)存在的類(lèi)型遵循(conform)一個(gè)協(xié)議
在 Swift 中,你甚至可以擴(kuò)展協(xié)議以提供其需要的實(shí)現(xiàn)磅摹,或者添加額外功能給遵循的類(lèi)型所使用滋迈。
注意:擴(kuò)展可以給一個(gè)類(lèi)型添加新的功能,但是不能重寫(xiě)已經(jīng)存在的功能偏瓤。
擴(kuò)展的語(yǔ)法
使用 extension 關(guān)鍵字聲明擴(kuò)展:
extension SomeType {
// 在這里給 SomeType 添加新的功能
}
擴(kuò)展可以擴(kuò)充一個(gè)現(xiàn)有的類(lèi)型杀怠,給它添加一個(gè)或多個(gè)協(xié)議。
協(xié)議名稱(chēng)的寫(xiě)法和類(lèi)或者結(jié)構(gòu)體一樣:
extension SomeType: SomeProtocol, AnotherProtocol {
// 協(xié)議所需要的實(shí)現(xiàn)寫(xiě)在這里
}
擴(kuò)展--計(jì)算型屬性
擴(kuò)展可以給現(xiàn)有類(lèi)型添加計(jì)算型實(shí)例屬性和計(jì)算型類(lèi)屬性厅克。
例:給 Swift 內(nèi)建的 Double 類(lèi)型添加了五個(gè)計(jì)算型實(shí)例屬性赔退,從而提供與距離單位相關(guān)工作的基本支持:
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印“One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印“Three feet is 0.914399970739201 meters”
注意:擴(kuò)展可以添加新的計(jì)算屬性,但是它們不能添加存儲(chǔ)屬性证舟,或向現(xiàn)有的屬性添加屬性觀察者硕旗。
擴(kuò)展--構(gòu)造器
擴(kuò)展可以給現(xiàn)有的類(lèi)型添加新的構(gòu)造器。
它使你可以把自定義類(lèi)型作為參數(shù)來(lái)供其他類(lèi)型的構(gòu)造器使用女责,或者在類(lèi)型的原始實(shí)現(xiàn)上添加額外的構(gòu)造選項(xiàng)漆枚。
擴(kuò)展--方法
擴(kuò)展可以給現(xiàn)有類(lèi)型添加新的實(shí)例方法和類(lèi)方法。
例 給 Int 類(lèi)型添加了一個(gè)新的實(shí)例方法叫做 repetitions:
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
擴(kuò)展--可變實(shí)例方法
通過(guò)擴(kuò)展添加的實(shí)例方法同樣也可以修改(或 mutating(改變))實(shí)例本身抵知。
結(jié)構(gòu)體和枚舉的方法墙基,若是可以修改 self 或者它自己的屬性软族,則必須將這個(gè)實(shí)例方法標(biāo)記為 mutating,就像是改變了方法的原始實(shí)現(xiàn)残制。
例:對(duì) Swift 的 Int 類(lèi)型添加了一個(gè)新的 mutating 方法立砸,叫做 square,它將原始值求平方:
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt 現(xiàn)在是 9
擴(kuò)展--下標(biāo)
擴(kuò)展可以給現(xiàn)有的類(lèi)型添加新的下標(biāo)初茶。
下面的例子中颗祝,對(duì) Swift 的 Int 類(lèi)型添加了一個(gè)整數(shù)類(lèi)型的下標(biāo)。下標(biāo) [n] 從數(shù)字右側(cè)開(kāi)始恼布,返回小數(shù)點(diǎn)后的第 n 位:
- 123456789[0] 返回 9
- 123456789[1] 返回 8
……以此類(lèi)推:
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7
如果操作的 Int 值沒(méi)有足夠的位數(shù)滿足所請(qǐng)求的下標(biāo)螺戳,那么下標(biāo)的現(xiàn)實(shí)將返回 0,將好像在數(shù)字的左邊補(bǔ)上了 0:
746381295[9]
// 返回 0折汞,就好像你進(jìn)行了這個(gè)請(qǐng)求:
0746381295[9]
擴(kuò)展--嵌套類(lèi)型
擴(kuò)展可以給現(xiàn)有的類(lèi)倔幼,結(jié)構(gòu)體,還有枚舉添加新的嵌套類(lèi)型:
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
22字支、協(xié)議
協(xié)議 定義了一個(gè)藍(lán)圖凤藏,規(guī)定了用來(lái)實(shí)現(xiàn)某一特定任務(wù)或者功能的方法、屬性堕伪,以及其他需要的東西。類(lèi)栗菜、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議欠雌,并為協(xié)議定義的這些要求提供具體實(shí)現(xiàn)。某個(gè)類(lèi)型能夠滿足某個(gè)協(xié)議的要求疙筹,就可以說(shuō)該類(lèi)型遵循這個(gè)協(xié)議富俄。
除了遵循協(xié)議的類(lèi)型必須實(shí)現(xiàn)的要求外,還可以對(duì)協(xié)議進(jìn)行擴(kuò)展而咆,通過(guò)擴(kuò)展來(lái)實(shí)現(xiàn)一部分要求或者實(shí)現(xiàn)一些附加功能霍比,這樣遵循協(xié)議的類(lèi)型就能夠使用這些功能。
協(xié)議語(yǔ)法
協(xié)議的定義方式與類(lèi)暴备、結(jié)構(gòu)體和枚舉的定義非常相似:
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
要讓自定義類(lèi)型遵循某個(gè)協(xié)議悠瞬,在定義類(lèi)型時(shí),需要在類(lèi)型名稱(chēng)后加上協(xié)議名稱(chēng)涯捻,中間以冒號(hào)(:)分隔浅妆。遵循多個(gè)協(xié)議時(shí),各協(xié)議之間用逗號(hào)(,)分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結(jié)構(gòu)體的定義部分
}
若是一個(gè)類(lèi)擁有父類(lèi)障癌,應(yīng)該將父類(lèi)放在遵循的協(xié)議名之前凌外,以逗號(hào)分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類(lèi)的定義部分
}
協(xié)議--屬性要求
協(xié)議可以要求遵循協(xié)議的類(lèi)型提供特定名稱(chēng)和類(lèi)型的實(shí)例屬性或類(lèi)型屬性。
協(xié)議不指定屬性是存儲(chǔ)屬性還是計(jì)算屬性涛浙,它只指定屬性的名稱(chēng)和類(lèi)型康辑。
協(xié)議還指定屬性是可讀的還是可讀可寫(xiě)的摄欲。
如果協(xié)議要求屬性是可讀可寫(xiě)的,那么該屬性不能是常量屬性或只讀的計(jì)算型屬性疮薇。
如果協(xié)議只要求屬性是可讀的胸墙,那么該屬性不僅可以是可讀的,如果代碼需要的話惦辛,還可以是可寫(xiě)的劳秋。
協(xié)議總是用 var 關(guān)鍵字來(lái)聲明變量屬性,在類(lèi)型聲明后加上 { set get } 來(lái)表示屬性是可讀可寫(xiě)的胖齐,可讀屬性則用 { get } 來(lái)表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協(xié)議中定義類(lèi)型屬性時(shí)玻淑,總是使用 static 關(guān)鍵字作為前綴。
當(dāng)類(lèi)類(lèi)型遵循協(xié)議時(shí)呀伙,除了 static 關(guān)鍵字补履,還可以使用 class 關(guān)鍵字來(lái)聲明類(lèi)型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
例 協(xié)議與遵循協(xié)議的結(jié)構(gòu)體和類(lèi)
protocol FullyNamed {
var fullName: String { get }
}
這是一個(gè)只有一個(gè)實(shí)例屬性要求的協(xié)議,任何遵循 FullyNamed 的類(lèi)型剿另,都必須有一個(gè)可讀的 String 類(lèi)型的實(shí)例屬性 fullName箫锤。
struct Person: FullyNamed {
var fullName: String
}
let join = Person(fullName: "John Appleseed")
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
協(xié)議--方法要求
協(xié)議可以要求遵循協(xié)議的類(lèi)型實(shí)現(xiàn)某些指定的實(shí)例方法或類(lèi)方法。
這些方法作為協(xié)議的一部分雨女,像普通方法一樣放在協(xié)議的定義中谚攒,但是不需要大括號(hào)和方法體。
可以在協(xié)議中定義具有可變參數(shù)的方法氛堕,和普通方法的定義方式相同馏臭。但是,不支持為協(xié)議中的方法提供默認(rèn)參數(shù)讼稚。
在協(xié)議中定義類(lèi)方法的時(shí)候括儒,總是使用 static 關(guān)鍵字作為前綴。即使在類(lèi)實(shí)現(xiàn)時(shí)锐想,類(lèi)方法要求使用 class 或 static 作為關(guān)鍵字前綴帮寻,前面的規(guī)則仍然適用:
protocol SomeProtocol {
static func someTypeMethod()
}
例
protocol RandomNumberGenerator {
func random() -> Double
}
RandomNumberGenerator 協(xié)議要求遵循協(xié)議的類(lèi)型必須擁有一個(gè)名為 random, 返回值類(lèi)型為 Double 的實(shí)例方法赠摇。
例:下面是一個(gè)遵循并符合 RandomNumberGenerator 協(xié)議的類(lèi)固逗。實(shí)現(xiàn)了一個(gè)叫做 線性同余生成器(linear congruential generator) 的偽隨機(jī)數(shù)算法。
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”
協(xié)議--異變方法要求
如果你在協(xié)議中定義了一個(gè)實(shí)例方法蝉稳,該方法會(huì)改變遵循該協(xié)議的類(lèi)型的實(shí)例抒蚜,那么在定義協(xié)議時(shí)需要在方法前加 mutating 關(guān)鍵字。這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求耘戚。
注意:實(shí)現(xiàn)協(xié)議中的 mutating 方法時(shí)嗡髓,若是類(lèi)類(lèi)型,則不用寫(xiě) mutating 關(guān)鍵字收津。而對(duì)于結(jié)構(gòu)體和枚舉饿这,則必須寫(xiě) mutating 關(guān)鍵字浊伙。
例:
protocol Togglable {
mutating func toggle()
}
Togglable 協(xié)議只定義了一個(gè)名為 toggle 的實(shí)例方法。
toggle() 方法將改變實(shí)例屬性长捧,從而切換遵循該協(xié)議類(lèi)型的實(shí)例的狀態(tài)嚣鄙。
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 現(xiàn)在的值為 .on
協(xié)議--構(gòu)造器要求
協(xié)議可以要求遵循協(xié)議的類(lèi)型實(shí)現(xiàn)指定的構(gòu)造器。
你可以像編寫(xiě)普通構(gòu)造器那樣串结,在協(xié)議的定義里寫(xiě)下構(gòu)造器的聲明哑子,但不需要寫(xiě)花括號(hào)和構(gòu)造器的實(shí)體:
protocol SomeProtocol {
init(someParameter: Int)
}
協(xié)議構(gòu)造器要求的類(lèi)實(shí)現(xiàn)
可以在遵循協(xié)議的類(lèi)中實(shí)現(xiàn)構(gòu)造器,無(wú)論是作為指定構(gòu)造器肌割,還是作為便利構(gòu)造器卧蜓。
無(wú)論哪種情況,你都必須為構(gòu)造器實(shí)現(xiàn)標(biāo)上 required 修飾符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
使用 required 修飾符可以確保所有子類(lèi)也必須提供此構(gòu)造器實(shí)現(xiàn)把敞,從而也能遵循協(xié)議弥奸。
注意:如果類(lèi)已經(jīng)被標(biāo)記為
final
,那么不需要在協(xié)議構(gòu)造器的實(shí)現(xiàn)中使用required
修飾符奋早,因?yàn)?final
類(lèi)不能有子類(lèi)盛霎。
如果一個(gè)子類(lèi)重寫(xiě)了父類(lèi)的指定構(gòu)造器,并且該構(gòu)造器滿足了某個(gè)協(xié)議的要求耽装,那么該構(gòu)造器的實(shí)現(xiàn)需要同時(shí)標(biāo)注 required 和 override 修飾符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因?yàn)樽裱瓍f(xié)議愤炸,需要加上 required
// 因?yàn)槔^承自父類(lèi),需要加上 override
required override init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
協(xié)議作為類(lèi)型
盡管協(xié)議本身并未實(shí)現(xiàn)任何功能掉奄,但是協(xié)議可以被當(dāng)做一個(gè)功能完備的類(lèi)型來(lái)使用摇幻。
協(xié)議作為類(lèi)型使用,有時(shí)被稱(chēng)作「存在類(lèi)型」挥萌,這個(gè)名詞來(lái)自「存在著一個(gè)類(lèi)型 T,該類(lèi)型遵循協(xié)議 T」枉侧。
協(xié)議可以像其他普通類(lèi)型一樣使用引瀑,使用場(chǎng)景如下:
- 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類(lèi)型或返回值類(lèi)型
- 作為常量榨馁、變量或?qū)傩缘念?lèi)型
- 作為數(shù)組憨栽、字典或其他容器中的元素類(lèi)型
下面是將協(xié)議作為類(lèi)型使用的例子:
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
協(xié)議--委托
委托是一種設(shè)計(jì)模式,它允許類(lèi)或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類(lèi)型的實(shí)例翼虫。
委托模式的實(shí)現(xiàn)很簡(jiǎn)單:定義協(xié)議來(lái)封裝那些需要被委托的功能屑柔,這樣就能確保遵循協(xié)議的類(lèi)型能提供這些功能。
委托模式可以用來(lái)響應(yīng)特定的動(dòng)作珍剑,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù)掸宛,而無(wú)需關(guān)心外部數(shù)據(jù)源的類(lèi)型。
在擴(kuò)展里添加協(xié)議遵循
在不修改源代碼的情況下招拙,可以通過(guò)擴(kuò)展令已有類(lèi)型遵循并符合協(xié)議唧瘾。
擴(kuò)展可以為已有類(lèi)型添加屬性措译、方法、下標(biāo)以及構(gòu)造器饰序,因此可以符合協(xié)議中的相應(yīng)要求领虹。
有條件地遵循協(xié)議
泛型類(lèi)型可能只在某些情況下滿足一個(gè)協(xié)議的要求,比如當(dāng)類(lèi)型的泛型形式參數(shù)遵循對(duì)應(yīng)協(xié)議時(shí)求豫。
你可以通過(guò)在擴(kuò)展類(lèi)型時(shí)列出限制讓泛型類(lèi)型有條件地遵循某協(xié)議塌衰。在你采納協(xié)議的名字后面寫(xiě)泛型 where 分句。
下面的擴(kuò)展讓 Array 類(lèi)型只要在存儲(chǔ)遵循 TextRepresentable 協(xié)議的元素時(shí)就遵循 TextRepresentable 協(xié)議蝠嘉。
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map {
$0.textualDescription
}
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"
在擴(kuò)展里聲明采納協(xié)議
當(dāng)一個(gè)類(lèi)型已經(jīng)遵循了某個(gè)協(xié)議中的所有要求最疆,卻還沒(méi)有聲明采納該協(xié)議時(shí),可以通過(guò)空的擴(kuò)展來(lái)讓它采納該協(xié)議:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
Hamster 的實(shí)例可以作為 TextRepresentable 類(lèi)型使用:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon”
使用合成實(shí)現(xiàn)來(lái)采納協(xié)議
協(xié)議類(lèi)型的集合
協(xié)議類(lèi)型可以在數(shù)組或者字典這樣的集合中使用是晨。
協(xié)議的繼承
協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議肚菠,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。
協(xié)議的繼承語(yǔ)法與類(lèi)的繼承相似罩缴,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協(xié)議的定義部分
}
類(lèi)專(zhuān)屬的協(xié)議
通過(guò)添加 AnyObject 關(guān)鍵字到協(xié)議的繼承列表蚊逢,就可以限制協(xié)議只能被類(lèi)類(lèi)型采納(以及非結(jié)構(gòu)體或者非枚舉的類(lèi)型)。
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// 這里是類(lèi)專(zhuān)屬協(xié)議的定義部分
}
協(xié)議合成
要求一個(gè)類(lèi)型同時(shí)遵循多個(gè)協(xié)議是很有用的箫章。
可以使用協(xié)議組合來(lái)復(fù)合多個(gè)協(xié)議到一個(gè)要求里烙荷。
協(xié)議組合行為就和你定義的臨時(shí)局部協(xié)議一樣擁有構(gòu)成中所有協(xié)議的需求。
協(xié)議組合不定義任何新的協(xié)議類(lèi)型檬寂。
協(xié)議組合使用 SomeProtocol & AnotherProtocol 的形式终抽。
你可以列舉任意數(shù)量的協(xié)議,用和符號(hào)(&)分開(kāi)桶至。
除了協(xié)議列表昼伴,協(xié)議組合也能包含類(lèi)類(lèi)型,這允許你標(biāo)明一個(gè)需要的父類(lèi)镣屹。
下面的例子中圃郊,將 Named 和 Aged 兩個(gè)協(xié)議按照上述語(yǔ)法組合成一個(gè)協(xié)議,作為函數(shù)參數(shù)的類(lèi)型:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
檢查協(xié)議一致性
可以使用 類(lèi)型轉(zhuǎn)換 中描述的 is
和 as
操作符來(lái)檢查協(xié)議一致性女蜈,即是否遵循某協(xié)議持舆,并且可以轉(zhuǎn)換到指定的協(xié)議類(lèi)型。
檢查和轉(zhuǎn)換協(xié)議的語(yǔ)法與檢查和轉(zhuǎn)換類(lèi)型是完全一樣的:
- is 用來(lái)檢查實(shí)例是否遵循某個(gè)協(xié)議伪窖,若遵循則返回 true逸寓,否則返回 false;
- as? 返回一個(gè)可選值覆山,當(dāng)實(shí)例遵循某個(gè)協(xié)議時(shí)竹伸,返回類(lèi)型為協(xié)議類(lèi)型的可選值,否則返回 nil
- 將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個(gè)協(xié)議類(lèi)型汹买,如果強(qiáng)轉(zhuǎn)失敗佩伤,將觸發(fā)運(yùn)行時(shí)錯(cuò)誤聊倔。
可選的協(xié)議要求
協(xié)議可以定義可選要求,遵循協(xié)議的類(lèi)型可以選擇是否實(shí)現(xiàn)這些要求。
在協(xié)議中使用 optional 關(guān)鍵字作為前綴來(lái)定義可選要求∠瑁可選要求用在你需要和 Objective-C 打交道的代碼中。
協(xié)議和可選要求都必須帶上 @objc 屬性甸陌。標(biāo)記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類(lèi)的類(lèi)或者 @objc 類(lèi)遵循,其他類(lèi)以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議盐股。
使用可選要求時(shí)(例如钱豁,可選的方法或者屬性),它們的類(lèi)型會(huì)自動(dòng)變成可選的疯汁。比如牲尺,一個(gè)類(lèi)型為 (Int) -> String 的方法會(huì)變成 ((Int) -> String)?。需要注意的是整個(gè)函數(shù)類(lèi)型是可選的幌蚊,而不是函數(shù)的返回值谤碳。
協(xié)議擴(kuò)展
協(xié)議可以通過(guò)擴(kuò)展來(lái)為遵循協(xié)議的類(lèi)型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)溢豆。
通過(guò)這種方式蜒简,你可以基于協(xié)議本身來(lái)實(shí)現(xiàn)這些功能,而無(wú)需在每個(gè)遵循協(xié)議的類(lèi)型中都重復(fù)同樣的實(shí)現(xiàn)漩仙,也無(wú)需使用全局函數(shù)搓茬。
例 擴(kuò)展 RandomNumberGenerator 協(xié)議來(lái)提供 randomBool() 方法。該方法使用協(xié)議中定義的 random() 方法來(lái)返回一個(gè)隨機(jī)的 Bool 值:
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
通過(guò)協(xié)議擴(kuò)展队他,所有遵循協(xié)議的類(lèi)型卷仑,都能自動(dòng)獲得這個(gè)擴(kuò)展所增加的方法實(shí)現(xiàn)而無(wú)需任何額外修改:
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”
協(xié)議擴(kuò)展可以為遵循協(xié)議的類(lèi)型增加實(shí)現(xiàn),但不能聲明該協(xié)議繼承自另一個(gè)協(xié)議麸折。協(xié)議的繼承只能在協(xié)議聲明處進(jìn)行指定系枪。
協(xié)議擴(kuò)展--提供默認(rèn)實(shí)現(xiàn)
可以通過(guò)協(xié)議擴(kuò)展來(lái)為協(xié)議要求的方法、計(jì)算屬性提供默認(rèn)的實(shí)現(xiàn)磕谅。
如果遵循協(xié)議的類(lèi)型為這些要求提供了自己的實(shí)現(xiàn),那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用雾棺。
注意:通過(guò)協(xié)議擴(kuò)展為協(xié)議要求提供的默認(rèn)實(shí)現(xiàn)和可選的協(xié)議要求不同膊夹。雖然在這兩種情況下,遵循協(xié)議的類(lèi)型都無(wú)需自己實(shí)現(xiàn)這些要求捌浩,但是通過(guò)擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)可以直接調(diào)用放刨,而無(wú)需使用可選鏈?zhǔn)秸{(diào)用。
為協(xié)議擴(kuò)展添加限制條件
在擴(kuò)展協(xié)議的時(shí)候尸饺,可以指定一些限制條件进统,只有遵循協(xié)議的類(lèi)型滿足這些限制條件時(shí)助币,才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。
這些限制條件寫(xiě)在協(xié)議名之后螟碎,使用 where 子句來(lái)描述眉菱。
23、泛型
泛型代碼讓你能根據(jù)自定義的需求掉分,編寫(xiě)出適用于任意類(lèi)型的俭缓、靈活可復(fù)用的函數(shù)及類(lèi)型。
泛型函數(shù)
func swapTwoValues<T>(_ a: inout T, _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}
泛型版本的函數(shù)使用占位符類(lèi)型名(這里叫做 T )酥郭,而不是 實(shí)際類(lèi)型名(例如 Int华坦、String 或 Double),占位符類(lèi)型名并不關(guān)心 T 具體的類(lèi)型不从,但它要求 a 和b 必須是相同的類(lèi)型惜姐,T 的實(shí)際類(lèi)型由每次調(diào)用 swapTwoValues(::) 來(lái)決定。
T 是占位類(lèi)型名椿息。
泛型--類(lèi)型參數(shù)
上面 swapTwoValues(::) 例子中歹袁,占位類(lèi)型 T 是一個(gè)類(lèi)型參數(shù)的例子,類(lèi)型參數(shù)指定并命名一個(gè)占位類(lèi)型撵颊,并且緊隨在函數(shù)名后面宇攻,使用一對(duì)尖括號(hào)括起來(lái)(例如 <T>)。
可提供多個(gè)類(lèi)型參數(shù)倡勇,將它們都寫(xiě)在尖括號(hào)中逞刷,用逗號(hào)分開(kāi)。
泛型類(lèi)型
類(lèi)型約束
類(lèi)型約束指定類(lèi)型參數(shù)必須繼承自指定類(lèi)妻熊、遵循特定的協(xié)議或協(xié)議組合夸浅。
類(lèi)型約束語(yǔ)法
在一個(gè)類(lèi)型參數(shù)名后面放置一個(gè)類(lèi)名或者協(xié)議名,并用冒號(hào)進(jìn)行分隔扔役,來(lái)定義類(lèi)型約束帆喇。
下面將展示泛型函數(shù)約束的基本語(yǔ)法(與泛型類(lèi)型的語(yǔ)法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數(shù)的函數(shù)體部分
}
泛型--關(guān)聯(lián)類(lèi)型
關(guān)聯(lián)類(lèi)型為協(xié)議中的某個(gè)類(lèi)型提供了一個(gè)占位符名稱(chēng),其代表的實(shí)際類(lèi)型在協(xié)議被遵循時(shí)才會(huì)被指定亿胸。
關(guān)聯(lián)類(lèi)型通過(guò) associatedtype 關(guān)鍵字來(lái)指定坯钦。
關(guān)聯(lián)類(lèi)型實(shí)踐
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container 協(xié)議定義了三個(gè)任何遵循該協(xié)議的類(lèi)型(即容器)必須提供的功能:
- 必須可以通過(guò) append(_:) 方法添加一個(gè)新元素到容器里。
- 必須可以通過(guò) count 屬性獲取容器中元素的數(shù)量侈玄,并返回一個(gè) Int 值婉刀。
- 必須可以通過(guò)索引值類(lèi)型為 Int 的下標(biāo)檢索到容器中的每一個(gè)元素。
泛型 Where 語(yǔ)句
可以通過(guò)定義一個(gè)泛型 where 子句來(lái)實(shí)現(xiàn)序仙。通過(guò)泛型 where 子句讓關(guān)聯(lián)類(lèi)型遵從某個(gè)特定的協(xié)議突颊,以及某個(gè)特定的類(lèi)型參數(shù)和關(guān)聯(lián)類(lèi)型必須類(lèi)型相同。
可以通過(guò)將 where 關(guān)鍵字緊跟在類(lèi)型參數(shù)列表后面來(lái)定義 where 子句,where 子句后跟一個(gè)或者多個(gè)針對(duì)關(guān)聯(lián)類(lèi)型的約束律秃,以及一個(gè)或多個(gè)類(lèi)型參數(shù)和關(guān)聯(lián)類(lèi)型間的相等關(guān)系爬橡。
可以在函數(shù)體或者類(lèi)型的大括號(hào)之前添加 where 子句。
24棒动、不透明類(lèi)型
25糙申、自動(dòng)引用計(jì)數(shù)
注意:引用計(jì)數(shù)僅僅應(yīng)用于類(lèi)的實(shí)例。結(jié)構(gòu)體和枚舉類(lèi)型是值類(lèi)型迁客,不是引用類(lèi)型郭宝,也不是通過(guò)引用的方式存儲(chǔ)和傳遞。
類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用
如果兩個(gè)類(lèi)實(shí)例互相持有對(duì)方的強(qiáng)引用掷漱,因而每個(gè)實(shí)例都讓對(duì)方一直存在粘室,就是這種情況。這就是所謂的循環(huán)強(qiáng)引用卜范。
解決實(shí)例之間的循環(huán)強(qiáng)引用
弱引用(weak reference)和無(wú)主引用(unowned reference)
弱引用和無(wú)主引用允許循環(huán)引用中的一個(gè)實(shí)例引用另一個(gè)實(shí)例而不保持強(qiáng)引用衔统。這樣實(shí)例能夠互相引用而不產(chǎn)生循環(huán)強(qiáng)引用。
使用場(chǎng)景:
當(dāng)其他的實(shí)例有更短的生命周期時(shí)海雪,使用弱引用(weak)锦爵,否則使用無(wú)主引用(unowned)。
弱引用
弱引用不會(huì)對(duì)其引用的實(shí)例保持強(qiáng)引用奥裸,不會(huì)阻止 ARC 銷(xiāo)毀被引用的實(shí)例险掀。這個(gè)特性阻止了引用變?yōu)檠h(huán)強(qiáng)引用。
聲明屬性或者變量時(shí)湾宙,在前面加上 weak 關(guān)鍵字表明這是一個(gè)弱引用樟氢。
弱引用不會(huì)保持所引用的實(shí)例,即使引用存在侠鳄,實(shí)例也有可能被銷(xiāo)毀埠啃。因此,ARC 會(huì)在引用的實(shí)例被銷(xiāo)毀后自動(dòng)將其弱引用賦值為 nil伟恶。并且因?yàn)槿跻眯枰谶\(yùn)行時(shí)允許被賦值為 nil碴开,所以它們會(huì)被定義為可選類(lèi)型變量,而不是常量博秫。
注意:當(dāng) ARC 設(shè)置弱引用為 nil 時(shí)潦牛,屬性觀察不會(huì)被觸發(fā)。
使用實(shí)例:
class Person {
let name: String
init(name: String) {
self.name = name
}
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
無(wú)主引用
和弱引用類(lèi)似挡育,無(wú)主引用不會(huì)牢牢保持住引用的實(shí)例罢绽。
和弱引用不同的是,無(wú)主引用在其他實(shí)例有相同或者更長(zhǎng)的生命周期時(shí)使用静盅。
你可以在聲明屬性或者變量時(shí),在前面加上關(guān)鍵字 unowned 表示這是一個(gè)無(wú)主引用。
無(wú)主引用通常都被期望擁有值蒿叠。不過(guò) ARC 無(wú)法在實(shí)例被銷(xiāo)毀后將無(wú)主引用設(shè)為 nil明垢,因?yàn)榉强蛇x類(lèi)型的變量不允許被賦值為 nil。
重點(diǎn):使用無(wú)主引用市咽,你必須確保引用始終指向一個(gè)未銷(xiāo)毀的實(shí)例痊银。
如果你試圖在實(shí)例被銷(xiāo)毀后,訪問(wèn)該實(shí)例的無(wú)主引用施绎,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤溯革。
無(wú)主引用和隱式解包可選值屬性
下面的例子,是第三種場(chǎng)景谷醉,兩個(gè)屬性都必須有值致稀,且初始化后永不會(huì)為nil,這種場(chǎng)景中俱尼,需要一個(gè)類(lèi)使用無(wú)主屬性抖单,另一個(gè)使用隱式解包可選值屬性。
這使兩個(gè)屬性初始化完成后能被直接訪問(wèn)(不需要可選解包)遇八,同時(shí)避免了循環(huán)引用矛绘。
下面的模型中,
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
通過(guò)一條語(yǔ)句同時(shí)創(chuàng)建 Country 和 City 的實(shí)例刃永,而不產(chǎn)生循環(huán)強(qiáng)引用货矮,且 capitalCity 屬性能被直接訪問(wèn),不需要通過(guò)感嘆號(hào)來(lái)解包它的可選值:
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印“Canada's capital city is called Ottawa”
閉包的循環(huán)強(qiáng)引用
循環(huán)強(qiáng)引用是在兩個(gè)類(lèi)實(shí)例屬性互相保持對(duì)方的強(qiáng)引用時(shí)產(chǎn)生的斯够。
循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類(lèi)實(shí)例的某個(gè)屬性囚玫,且這個(gè)閉包體中又使用了這個(gè)類(lèi)實(shí)例時(shí)。
閉包和類(lèi)相似雳刺,都是引用類(lèi)型劫灶,所以會(huì)產(chǎn)生循環(huán)引用。
Swift 提供了一種優(yōu)雅的方法來(lái)解決這個(gè)問(wèn)題掖桦,稱(chēng)之為 閉包捕獲列表(closure capture list)本昏。
解決閉包的循環(huán)強(qiáng)引用
在定義閉包時(shí)同時(shí)定義捕獲列表作為閉包的一部分,通過(guò)這種方式解決閉包和類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用枪汪。
捕獲列表定義了閉包體內(nèi)捕獲一個(gè)或多個(gè)引用類(lèi)型的規(guī)則涌穆。聲明每個(gè)捕獲的引用為弱引用或無(wú)主引用,而不是強(qiáng)引用雀久。
注意:只要在閉包內(nèi)使用 self 的成員宿稀,就要用 self.someProperty 或者 self.someMethod()(而不只是 someProperty 或 someMethod())。這提醒你可能會(huì)一不小心就捕獲了 self赖捌。
定義捕獲列表
捕獲列表中的每一項(xiàng)都由一對(duì)元素組成祝沸,一個(gè)元素是 weak 或 unowned 關(guān)鍵字矮烹,另一個(gè)元素是類(lèi)實(shí)例的引用(例如 self)或初始化過(guò)的變量(如 delegate = self.delegate)。這些項(xiàng)在方括號(hào)中用逗號(hào)分開(kāi)罩锐。
如果閉包有參數(shù)列表和返回類(lèi)型奉狈,把捕獲列表放在它們前面:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
如果閉包沒(méi)有指明參數(shù)列表或者返回類(lèi)型,它們會(huì)通過(guò)上下文推斷涩惑,那么可以把捕獲列表和關(guān)鍵字 in 放在閉包最開(kāi)始的地方:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// 這里是閉包的函數(shù)體
}
弱引用和無(wú)主引用
在閉包和捕獲的實(shí)例總是互相引用并且總是同時(shí)銷(xiāo)毀時(shí)仁期,將比包內(nèi)的捕獲定義為無(wú)主引用。
在被捕獲的引用可能變?yōu)?nil 時(shí)竭恬,將閉包內(nèi)的捕獲定義為弱引用跛蛋。
弱引用總是可選類(lèi)型,并且當(dāng)引用的實(shí)例被銷(xiāo)毀后痊硕,弱引用的值會(huì)自動(dòng)設(shè)置為nil赊级。
注意:如果被捕獲的引用絕對(duì)不會(huì)變?yōu)?nil,應(yīng)該用無(wú)主引用寿桨,而不是弱引用此衅。
26、內(nèi)存安全
瞬時(shí)訪問(wèn):一個(gè)訪問(wèn)不可能在其訪問(wèn)期間被其它代碼訪問(wèn)亭螟,這就是一個(gè)瞬時(shí)訪問(wèn)挡鞍。正常情況下,兩個(gè)瞬時(shí)訪問(wèn)是不可能同時(shí)發(fā)生的预烙。大多數(shù)內(nèi)存訪問(wèn)都是瞬時(shí)的墨微。
長(zhǎng)期訪問(wèn):在別的代碼執(zhí)行時(shí)持續(xù)訪問(wèn),這種就是長(zhǎng)期訪問(wèn)扁掸。
瞬時(shí)訪問(wèn)和長(zhǎng)期訪問(wèn)的區(qū)別在于別的代碼有沒(méi)有可能在訪問(wèn)期間同時(shí)訪問(wèn)翘县,也就是在時(shí)間線上的重疊。一個(gè)長(zhǎng)期訪問(wèn)可以被別的長(zhǎng)期訪問(wèn)或瞬時(shí)訪問(wèn)重疊谴分。
重疊的訪問(wèn)主要出現(xiàn)在使用 in-out 參數(shù)的函數(shù)和方法或者結(jié)構(gòu)體的 mutating 方法里锈麸。
In-Out 參數(shù)的訪問(wèn)沖突
一個(gè)函數(shù)會(huì)對(duì)它所有的 in-out 參數(shù)進(jìn)行長(zhǎng)期寫(xiě)訪問(wèn)。in-out 參數(shù)的寫(xiě)訪問(wèn)會(huì)在所有非 in-out 參數(shù)處理完之后開(kāi)始牺蹄,直到函數(shù)執(zhí)行完畢為止忘伞。如果有多個(gè) in-out 參數(shù),則寫(xiě)訪問(wèn)開(kāi)始的順序與參數(shù)的順序一致沙兰。
長(zhǎng)期訪問(wèn)的存在會(huì)造成一個(gè)結(jié)果氓奈,你不能在訪問(wèn)以 in-out 形式傳入后的原變量,即使作用域原則和訪問(wèn)權(quán)限允許——任何訪問(wèn)原變量的行為都會(huì)造成沖突鼎天。例如:
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// 錯(cuò)誤:stepSize 訪問(wèn)沖突
stepSize 的讀訪問(wèn)和寫(xiě)訪問(wèn)重疊了舀奶。
解決這個(gè)沖突的一種方式,是顯示拷貝一份 stepSize :
// 顯式拷貝
var copyOfStepSize = stepSize
increment(©OfStepSize)
// 更新原來(lái)的值
stepSize = copyOfStepSize
// stepSize 現(xiàn)在的值是 2
長(zhǎng)期寫(xiě)訪問(wèn)的存在還會(huì)造成另一種結(jié)果斋射,往同一個(gè)函數(shù)的多個(gè) in-out 參數(shù)里傳入同一個(gè)變量也會(huì)產(chǎn)生沖突育勺,例如:
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // 正常
balance(&playerOneScore, &playerOneScore)
// 錯(cuò)誤:playerOneScore 訪問(wèn)沖突
方法里 self 的訪問(wèn)沖突
一個(gè)結(jié)構(gòu)體的 mutating 方法會(huì)在調(diào)用期間對(duì) self 進(jìn)行寫(xiě)訪問(wèn)但荤。此時(shí)方法內(nèi)其它代碼就不能對(duì)self對(duì)應(yīng)的實(shí)例屬性發(fā)起重疊訪問(wèn)。
屬性的訪問(wèn)沖突
如結(jié)構(gòu)體涧至,元組和枚舉的類(lèi)型都是由多個(gè)獨(dú)立的值組成的纱兑,例如結(jié)構(gòu)體的屬性或元組的元素。
因?yàn)樗鼈兌际侵殿?lèi)型化借,修改值的任何一部分都是對(duì)于整個(gè)值的修改,意味著其中一個(gè)屬性的讀或?qū)懺L問(wèn)都需要訪問(wèn)整一個(gè)值捡多。
27蓖康、訪問(wèn)控制
訪問(wèn)控制可以限定其它源文件或模塊對(duì)你的代碼的訪問(wèn)。
可以明確地給單個(gè)類(lèi)型(類(lèi)垒手、結(jié)構(gòu)體蒜焊、枚舉)設(shè)置訪問(wèn)級(jí)別,也可以給這些類(lèi)型的屬性科贬、方法泳梆、構(gòu)造器、下標(biāo)等設(shè)置訪問(wèn)級(jí)別榜掌。協(xié)議也可以被限定在一定訪問(wèn)級(jí)別的范圍內(nèi)使用优妙,包括協(xié)議里的全局常量、變量和函數(shù)憎账。
模塊和源文件
Swift 中的訪問(wèn)控制模型基于模塊和源文件這兩個(gè)概念套硼。
模塊指的是獨(dú)立的代碼單元,框架或應(yīng)用程序會(huì)作為一個(gè)獨(dú)立的模塊來(lái)構(gòu)建和發(fā)布胞皱。在 Swift 中邪意,一個(gè)模塊可以使用 import 關(guān)鍵字導(dǎo)入另外一個(gè)模塊。
在 Swift 中反砌,Xcode 的每個(gè) target(例如框架或應(yīng)用程序)都被當(dāng)作獨(dú)立的模塊處理雾鬼。如果你是為了實(shí)現(xiàn)某個(gè)通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨(dú)立的框架宴树,這個(gè)框架就是 Swift 中的一個(gè)模塊策菜。當(dāng)它被導(dǎo)入到某個(gè)應(yīng)用程序或者其他框架時(shí),框架的內(nèi)容都將屬于這個(gè)獨(dú)立的模塊森渐。
源文件 就是 Swift 模塊中的源代碼文件(實(shí)際上做入,源文件屬于一個(gè)應(yīng)用程序或框架)。盡管我們一般會(huì)將不同的類(lèi)型分別定義在不同的源文件中同衣,但是同一個(gè)源文件也可以包含多個(gè)類(lèi)型竟块、函數(shù)等的定義。
訪問(wèn)級(jí)別
Swift 為代碼中的實(shí)體提供了五種不同的訪問(wèn)級(jí)別耐齐。
- open 和 public 級(jí)別可以讓實(shí)體被同一模塊源文件中的所有實(shí)體訪問(wèn)浪秘,在模塊外也可以通過(guò)導(dǎo)入該模塊來(lái)訪問(wèn)源文件里的所有實(shí)體蒋情。通常情況下,你會(huì)使用 open 或 public 級(jí)別來(lái)指定框架的外部接口耸携。
open 和 public 的區(qū)別:open 只能作用于類(lèi)和類(lèi)的成員棵癣。open 限定的類(lèi)和成員能夠在模塊外被繼承和重寫(xiě)。將類(lèi)的訪問(wèn)級(jí)別顯示指定為 open 表明你已經(jīng)設(shè)計(jì)好了類(lèi)的代碼夺衍,且充分考慮過(guò)這個(gè)類(lèi)在其他模塊中作用于父類(lèi)時(shí)的影響狈谊。 - internal 級(jí)別讓實(shí)體被同一模塊源文件中的任何實(shí)體訪問(wèn),但是不能被模塊外的實(shí)體訪問(wèn)沟沙。通常情況下河劝,如果某個(gè)接口只在應(yīng)用程序或框架內(nèi)部使用,就可以將其設(shè)置為 internal 級(jí)別矛紫。
fileprivate 限制實(shí)體只能在其定義的文件內(nèi)部訪問(wèn)赎瞎。如果功能的部分實(shí)現(xiàn)細(xì)節(jié)只需要在文件內(nèi)使用時(shí),可以使用 fileprivate 來(lái)將其隱藏颊咬。 - private 限制實(shí)體只能在其定義的作用域务甥,以及同一文件內(nèi)的 extension 訪問(wèn)。如果功能的部分細(xì)節(jié)只需要在當(dāng)前作用域內(nèi)使用時(shí)喳篇,可以使用 private 來(lái)將其隱藏敞临。
open 為最高訪問(wèn)級(jí)別(限制最少),private 為最低訪問(wèn)級(jí)別(限制最多)杭隙。
訪問(wèn)級(jí)別基本原則
Swift 中的訪問(wèn)級(jí)別遵循一個(gè)基本原則:實(shí)體不能定義在具有更低訪問(wèn)級(jí)別(更嚴(yán)格)的實(shí)體中哟绊。
例:
- 一個(gè) public 的變量,其類(lèi)型的訪問(wèn)級(jí)別不能是 internal痰憎,fileprivate 或是 private票髓。因?yàn)闊o(wú)法保證變量的類(lèi)型在使用變量的地方也具有訪問(wèn)權(quán)限。
- 函數(shù)的訪問(wèn)級(jí)別不能高于它的參數(shù)類(lèi)型和返回類(lèi)型的訪問(wèn)級(jí)別铣耘。因?yàn)檫@樣就會(huì)出現(xiàn)函數(shù)可以在任何地方被訪問(wèn)洽沟,但是它的參數(shù)類(lèi)型和返回類(lèi)型卻不可以的情況。
默認(rèn)訪問(wèn)級(jí)別
如果你不顯式的指定它們的訪問(wèn)級(jí)別蜗细,那么它們將都有一個(gè) internal 的默認(rèn)訪問(wèn)級(jí)別裆操。
子類(lèi)訪問(wèn)級(jí)別
子類(lèi)的訪問(wèn)級(jí)別不得高于父類(lèi)的訪問(wèn)級(jí)別。
常量炉媒、變量踪区、屬性、下標(biāo)
常量吊骤、變量缎岗、屬性、下標(biāo)不能擁有比它們的類(lèi)型更高的訪問(wèn)級(jí)別白粉。
Getter 和 Setter
常量传泊、變量鼠渺、屬性、下標(biāo)的 Getters 和 Setters 的訪問(wèn)級(jí)別和它們所屬類(lèi)型的訪問(wèn)級(jí)別相同眷细。
Setter 的訪問(wèn)級(jí)別可以低于對(duì)應(yīng)的 Getter 的訪問(wèn)級(jí)別拦盹,這樣就可以控制變量、屬性或下標(biāo)的讀寫(xiě)權(quán)限溪椎。在 var 或 subscript 關(guān)鍵字之前普舆,你可以通過(guò) fileprivate(set),private(set) 或 internal(set) 為它們的寫(xiě)入權(quán)限指定更低的訪問(wèn)級(jí)別校读。
類(lèi)型別名
你定義的任何類(lèi)型別名都會(huì)被當(dāng)作不同的類(lèi)型奔害,以便于進(jìn)行訪問(wèn)控制。類(lèi)型別名的訪問(wèn)級(jí)別不可高于其表示的類(lèi)型的訪問(wèn)級(jí)別