Swift基礎(chǔ)知識(shí)記錄

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)
}

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)換 中描述的 isas 操作符來(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)型

參考不透明類(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(&copyOfStepSize)

// 更新原來(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í)別

28地熄、高級(jí)運(yùn)算符(參考
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芯杀,隨后出現(xiàn)的幾起案子端考,更是在濱河造成了極大的恐慌,老刑警劉巖揭厚,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件却特,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡筛圆,警方通過(guò)查閱死者的電腦和手機(jī)裂明,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)太援,“玉大人闽晦,你說(shuō)我怎么就攤上這事√岵恚” “怎么了仙蛉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碱蒙。 經(jīng)常有香客問(wèn)我荠瘪,道長(zhǎng),這世上最難降的妖魔是什么赛惩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任哀墓,我火速辦了婚禮,結(jié)果婚禮上喷兼,老公的妹妹穿的比我還像新娘篮绰。我一直安慰自己,他們只是感情好褒搔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布阶牍。 她就那樣靜靜地躺著喷面,像睡著了一般。 火紅的嫁衣襯著肌膚如雪走孽。 梳的紋絲不亂的頭發(fā)上惧辈,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音磕瓷,去河邊找鬼盒齿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛困食,可吹牛的內(nèi)容都是我干的边翁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼硕盹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼符匾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起瘩例,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啊胶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后垛贤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體焰坪,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年聘惦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了某饰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡善绎,死狀恐怖黔漂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情禀酱,我是刑警寧澤瘟仿,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站比勉,受9級(jí)特大地震影響劳较,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浩聋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一观蜗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衣洁,春花似錦墓捻、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撤卢。三九已至,卻和暖如春梧兼,著一層夾襖步出監(jiān)牢的瞬間放吩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工羽杰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渡紫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓考赛,卻偏偏與公主長(zhǎng)得像惕澎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颜骤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容