swift4.0 學(xué)習(xí)筆記

標(biāo)簽(空格分隔): 未分類


基礎(chǔ)(相關(guān)概念)

1.元祖

元組(tuples)把多個(gè)值組合成一個(gè)復(fù)合值。元組內(nèi)的值可以是任意類型,并不要求是相同類型

let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")

2.可選類型(optionals)

使用可選類型(optionals)來處理值可能缺失的情況??蛇x類型表示:
有值,等于 x;或者 沒有值

C 和 Objective-C 中并沒有可選類型這個(gè)概念。最接近的是 Objective-C 中的一個(gè)特性,一個(gè)方法要不返回一個(gè)對(duì)象要不返回nil,nil表示“缺少一個(gè)合法的對(duì)象”。然而,這只對(duì)對(duì)象起作用——對(duì)于結(jié)構(gòu)體,基本的 C 類型或者枚舉類型不起作用。對(duì)于這些類型,Objective-C 方法一般會(huì)返回一個(gè)特殊值(比如NSNotFound)來暗示值缺失。這種方法假設(shè)方法的調(diào)用者知道并記得對(duì)特殊值進(jìn)行判斷。然而,Swift 的可選類型可以讓你暗示任意類型的值缺失,并不需要一個(gè)特殊值。

//1.變量聲明
var surveyAnswer: String?
// surveyAnswer 被自動(dòng)設(shè)置為 nil

//2.自動(dòng)類型推斷
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測(cè)為類型 "Int?", 或者類型 "optional Int"

//3.可選類型賦值(兩種狀態(tài))
var serverResponseCode: Int? = 404
// serverResponseCode 包含一個(gè)可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值

if 語句以及強(qiáng)制解析
你可以使用 if 語句和 nil比較來判斷一個(gè)可選值是否包含值。你可以使用“相等”(==)或“不等”(!=)來執(zhí)行比較。如果可選類型有值,它將不等于 nil:

if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// 輸出 "convertedNumber contains some integer value."

當(dāng)你確定可選類型確實(shí)包含值之后,你可以在可選的名字后面加一個(gè)感嘆號(hào)(!)來獲取值。這個(gè)驚嘆號(hào)表示“我知道這個(gè)可選有值,請(qǐng)使用它。”這被稱為可選值的強(qiáng)制解析(forced unwrapping):

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 輸出 "convertedNumber has an integer value of 123."

可選綁定
使用可選綁定(optional binding)來判斷可選類型是否包含值,如果包含就把值賦給一個(gè)臨時(shí)常量或者變量??蛇x綁定可以用在 if 和 while 語句中,這條語句不僅可以用來判斷可選類型中是否有值,同時(shí)可以將可選類型中的值賦給一個(gè)常量或者變量。

if let constantName = someOptional {
    statements
}

3.錯(cuò)誤處理

swift是類型安全的語言,對(duì)于錯(cuò)誤中斷必須處理,而OC在這方面沒有如此嚴(yán)格的要求

//OC不帶error處理
[NSFileManager.defaultManager copyItemAtURL:[NSURL fileURLWithPath:@""] toURL:[NSURL fileURLWithPath:@""] error:nil];

//OC帶error處理(非強(qiáng)制)
NSError *error;
[NSFileManager.defaultManager copyItemAtURL:[NSURL fileURLWithPath:@""] toURL:[NSURL fileURLWithPath:@""] error:&error];
//swift
do{
    try FileManager.default.copyItem(at: URL.init(fileURLWithPath: ""), to:URL.init(fileUrlWithPath:"")
}catch{
    //error handle
}

集合類型

1.數(shù)組(Array)

創(chuàng)建數(shù)組

//1.創(chuàng)建空數(shù)組
var someInts = [Int]()

//2.創(chuàng)建帶有默認(rèn)值或指定長度的數(shù)組
var threeDoubles = Array(repeating: 0.0, count: 3)

//3.創(chuàng)建一個(gè)含有數(shù)據(jù)的數(shù)組()
var shoppingList: [String] = ["Eggs", "Milk"]
var shoppingList = ["Eggs", "Milk"] 
注:兩者等價(jià),自動(dòng)類型推斷,類型為[String]

訪問和修改數(shù)組

var shoppingList = [String][]

//1.拼接元素
shoppingList.append("egg")

//2.拼接數(shù)組
shoppingList = shoppingList + ["tomato","milk"]

//3.修改元素
shoppingList[0] = "orange juice"
shoppingList[0...2] = ["fish","noodle","pork","onion"]
注:可將其中的某幾項(xiàng)替換成某幾項(xiàng)(數(shù)量可不對(duì)等)

//4.插入元素
shoppingList.insert("Maple Syrup", at: 0)

//5.移除元素
shoppingList.remove(at:2)
shoppingList.removeLast()
shoppingList.removeFirst()

//6.清除所有元素
shoppingList = []

數(shù)組遍歷

for item in shoppingList {
    print(item)
}

//帶索引的遍歷
for (index, value) in shoppingList. enumerated() {
    print("Item \(String(index + 1)): \(value)")
}

2.集合(Set)

集合創(chuàng)建

var letters = Set<String>()
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] //自動(dòng)推斷集合元素?cái)?shù)據(jù)類型

集合賦值、遍歷

favoriteGenres.insert("Jazz")
favoriteGenres.remove("Jazz")
for genre in favoriteGenres {
    print("\(genre)")
}

集合操作

  • 使用intersection(_:)方法根據(jù)兩個(gè)集合中都包含的值創(chuàng)建的一個(gè)新的集合。
  • 使用symmetricDifference(_:)方法根據(jù)在一個(gè)集合中但不在兩個(gè)集合中的值創(chuàng)建一個(gè)新的集合。
  • 使用union(_:)方法根據(jù)兩個(gè)集合的值創(chuàng)建一個(gè)新的集合。
  • 使用subtracting(_:)方法根據(jù)不在該集合中的值創(chuàng)建一個(gè)新的集合。

[圖片上傳失敗...(image-899197-1524110403362)]

let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits. intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]

3.字典(Dictionary)

創(chuàng)建字典

var namesOfIntegers = [Int: String]()
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
//類型自動(dòng)推導(dǎo)為[String: String]

訪問和修改字典

airports["LHR"] = "London" //賦值
airports["APL"] = nil //移除鍵值對(duì)
airports.removeValue(forKey: "DUB")//移除鍵值對(duì)
airports.count //字典鍵值對(duì)數(shù)量
airports.isEmpty //字典是否為空

注:我們也可以使用下標(biāo)語法來在字典中檢索特定鍵對(duì)應(yīng)的值。因?yàn)橛锌赡苷?qǐng)求的鍵沒有對(duì)應(yīng)的值存在,字典的下標(biāo)訪問會(huì)返回對(duì)應(yīng)值的類型的可選值。如果這個(gè)字典包含請(qǐng)求鍵所對(duì)應(yīng)的值,下標(biāo)會(huì)返回一個(gè)包含這個(gè)存在值的可選值,否則將返回nil:

//做可選判斷是非常必要的
if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} else {
    print("That airport is not in the airports dictionary.")
}

字典遍歷

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}

函數(shù)

1.函數(shù)的定義與調(diào)用

函數(shù)由 func + 函數(shù)名 + 函數(shù)參數(shù)(0-n個(gè)) + 返回參數(shù)(可為Void,返回參數(shù)可為元祖) 構(gòu)成

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}

指定參數(shù)標(biāo)簽:你可以在參數(shù)名稱前指定它的參數(shù)標(biāo)簽,中間以空格分隔:

func someFunction(argumentLabel parameterName: Int) {
    // 在函數(shù)體內(nèi),parameterName 代表參數(shù)值,argumentLabele為參數(shù)標(biāo)簽
}

忽略參數(shù)標(biāo)簽:如果你不希望為某個(gè)參數(shù)添加一個(gè)標(biāo)簽,可以使用一個(gè)下劃線(_)來代替一個(gè)明確的參數(shù)標(biāo)簽

func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
     // 在函數(shù)體內(nèi),firstParameterName 和 secondParameterName 代表參數(shù)中的第一個(gè)和第二個(gè)參數(shù)值
}
someFunction(1, secondParameterName: 2)

默認(rèn)參數(shù)值:你可以在函數(shù)體中通過給參數(shù)賦值來為任意一個(gè)參數(shù)定義默認(rèn)值(Deafult Value)。當(dāng)默認(rèn)值被定義后,調(diào)用這個(gè)函數(shù)時(shí)可以忽略這個(gè)參數(shù)

func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
    // 如果你在調(diào)用時(shí)候不傳第二個(gè)參數(shù),parameterWithDefault 會(huì)值為 12 傳入到函數(shù)體中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12

可變參數(shù):一個(gè)可變參數(shù)(variadic parameter)可以接受零個(gè)或多個(gè)值。函數(shù)調(diào)用時(shí),你可以用可變參數(shù)來指定函數(shù)參數(shù)可以被傳入不確定數(shù)量的輸入值。通過在變量類型名后面加入(...)的方式來定義可變參數(shù)

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是這 5 個(gè)數(shù)的平均數(shù)。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是這 3 個(gè)數(shù)的平均數(shù)。

函數(shù)的使用

你可以定義一個(gè)類型為函數(shù)的常量或變量

var mathFunction: (Int, Int) -> Int = addTwoInts

函數(shù)類型作為參數(shù)類型

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}

函數(shù)類型作為返回類型

func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

閉包

閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數(shù)比較相似。
閉包可以捕獲和存儲(chǔ)其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量。 Swift 會(huì)為你管理在捕獲過程中涉及到的所有內(nèi)存操作。

尾隨閉包

如果你需要將一個(gè)很長的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性。尾隨閉包是一個(gè)書寫在函數(shù)括號(hào)之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。在使用尾隨閉包時(shí),你不用寫出它的參數(shù)標(biāo)簽:

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函數(shù)體部分
}

// 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure(closure: {
    // 閉包主體部分
})

// 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure() {
    // 閉包主體部分
}

值捕獲

閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。

Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

注:incrementer() 函數(shù)并沒有任何參數(shù),但是在函數(shù)體內(nèi)訪問了 runningTotal 和 amount 變量。這是因?yàn)樗鼜耐鈬瘮?shù)捕獲了 runningTotal 和 amount 變量的引用。捕獲引用保證了 runningTotal 和 amount 變量在調(diào)用完 makeIncrementer 后不會(huì)消失,并且保證了在下一次執(zhí)行 incrementer 函數(shù)時(shí),runningTotal 依舊存在。

逃逸閉包

當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時(shí),你可以在參數(shù)名之前標(biāo)注 @escaping,用來指明這個(gè)閉包是允許“逃逸”出這個(gè)函數(shù)的。

一種能使閉包“逃逸”出函數(shù)的方法是,將這個(gè)閉包保存在一個(gè)函數(shù)外部定義的變量中。舉個(gè)例子,很多啟動(dòng)異步操作的函數(shù)接受一個(gè)閉包參數(shù)作為 completion handler。這類函數(shù)會(huì)在異步操作開始之后立刻返回,但是閉包直到異步操作結(jié)束后才會(huì)被調(diào)用。在這種情況下,閉包需要“逃逸”出函數(shù),因?yàn)殚]包需要在函數(shù)返回之后被調(diào)用。例如:

//逃逸閉包
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
//非逃逸閉包
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

將一個(gè)閉包標(biāo)記為 @escaping 意味著你必須在閉包中顯式地引用 self。比如說,在下面的代碼中,傳遞到 someFunctionWithEscapingClosure(:) 中的閉包是一個(gè)逃逸閉包,這意味著它需要顯式地引用 self。相對(duì)的,傳遞到 someFunctionWithNonescapingClosure(:) 中的閉包是一個(gè)非逃逸閉包,這意味著它可以隱式引用 self。

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"

completionHandlers.first?()
print(instance.x)
// 打印出 "100"

自動(dòng)閉包

自動(dòng)閉包是一種自動(dòng)創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式。這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時(shí)候,會(huì)返回被包裝在其中的表達(dá)式的值。這種便利語法讓你能夠省略閉包的花括號(hào),用一個(gè)普通的表達(dá)式來代替顯式的閉包。

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

閉包作為函數(shù)參數(shù):

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
//顯式閉包傳參
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
//自動(dòng)閉包傳參
serve(customer: customersInLine.remove(at: 0))
// 打印 "Now serving Ewa!"

枚舉

枚舉為一組相關(guān)的值定義了一個(gè)共同的類型,使你可以在你的代碼中以類型安全的方式來使用這些值。

enum CompassPoint {
    case north
    case south
    case east
    case west
}
enum CompassPoint {
    case north,south,east,west
}

使用 Switch 語句匹配枚舉值
你可以使用switch語句匹配單個(gè)枚舉值:

directionToHead = CompassPoint.south
switch directionToHead {
    case .north:
        print("Lots of planets have a north")
    case .south:
        print("Watch out for penguins")
    case .east:
        print("Where the sun rises")
    case .west:
        print("Where the skies are blue")
}
// 打印 "Watch out for penguins”

遞歸枚舉

遞歸枚舉是一種枚舉類型,它有一個(gè)或多個(gè)枚舉成員使用該枚舉類型的實(shí)例作為關(guān)聯(lián)值。使用遞歸枚舉時(shí),編譯器會(huì)插入一個(gè)間接層。你可以在枚舉成員前加上indirect來表示該成員可遞歸。

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

//也可以在枚舉類型開頭加上indirect關(guān)鍵字來表明它的所有成員都是可遞歸的:
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

例子:

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

屬性

屬性將值跟特定的類、結(jié)構(gòu)或枚舉關(guān)聯(lián)。存儲(chǔ)屬性存儲(chǔ)常量或變量作為實(shí)例的一部分,而計(jì)算屬性計(jì)算(不是存儲(chǔ))一個(gè)值。計(jì)算屬性可以用于類、結(jié)構(gòu)體和枚舉,存儲(chǔ)屬性只能用于類和結(jié)構(gòu)體。

存儲(chǔ)屬性

簡單來說,一個(gè)存儲(chǔ)屬性就是存儲(chǔ)在特定類或結(jié)構(gòu)體實(shí)例里的一個(gè)常量或變量。存儲(chǔ)屬性可以是變量存儲(chǔ)屬性(用關(guān)鍵字 var 定義),也可以是常量存儲(chǔ)屬性(用關(guān)鍵字 let 定義)。

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

延遲存儲(chǔ)屬性(OC的懶加載)

延遲存儲(chǔ)屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會(huì)計(jì)算其初始值的屬性。在屬性聲明前使用 lazy 來標(biāo)示一個(gè)延遲存儲(chǔ)屬性。

注意:必須將延遲存儲(chǔ)屬性聲明成變量(使用 var 關(guān)鍵字),因?yàn)閷傩缘某跏贾悼赡茉趯?shí)例構(gòu)造完成之后才會(huì)得到。而常量屬性在構(gòu)造過程完成之前必須要有初始值,因此無法聲明成延遲屬性。

延遲屬性很有用,當(dāng)屬性的值依賴于在實(shí)例的構(gòu)造過程結(jié)束后才會(huì)知道影響值的外部因素時(shí),或者當(dāng)獲得屬性的初始值需要復(fù)雜或大量計(jì)算時(shí),可以只在需要的時(shí)候計(jì)算它。

class DataImporter {
    /*
    DataImporter 是一個(gè)負(fù)責(zé)將外部文件中的數(shù)據(jù)導(dǎo)入的類。
    這個(gè)類的初始化會(huì)消耗不少時(shí)間。
    */
    var fileName = "data.txt"
    // 這里會(huì)提供數(shù)據(jù)導(dǎo)入功能
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 這里會(huì)提供數(shù)據(jù)管理功能
}

計(jì)算屬性

除存儲(chǔ)屬性外,類、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲(chǔ)值,而是提供一個(gè) getter 和一個(gè)可選的 setter,來間接獲取和設(shè)置其他屬性或變量的值。

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

只讀計(jì)算屬性

只有 getter 沒有 setter 的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值,可以通過點(diǎn)運(yùn)算符訪問,但不能設(shè)置新的值。

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

屬性觀察器

屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會(huì)調(diào)用屬性觀察器,即使新值和當(dāng)前值相同的時(shí)候也不例外。

可以為除了延遲存儲(chǔ)屬性之外的其他存儲(chǔ)屬性添加屬性觀察器,也可以通過重寫屬性的方式為繼承的屬性(包括存儲(chǔ)屬性和計(jì)算屬性)添加屬性觀察器。你不必為非重寫的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^它的 setter 直接監(jiān)控和響應(yīng)值的變化。

  • willSet 在新的值被設(shè)置之前調(diào)用
  • didSet 在新的值被設(shè)置之后立即調(diào)用
 class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

類型屬性

實(shí)例屬性屬于一個(gè)特定類型的實(shí)例,每創(chuàng)建一個(gè)實(shí)例,實(shí)例都擁有屬于自己的一套屬性值,實(shí)例之間的屬性相互獨(dú)立。

也可以為類型本身定義屬性,無論創(chuàng)建了多少個(gè)該類型的實(shí)例,這些屬性都只有唯一一份。這種屬性就是類型屬性。

類型屬性用于定義某個(gè)類型所有實(shí)例共享的數(shù)據(jù),比如所有實(shí)例都能用的一個(gè)常量(就像 C 語言中的靜態(tài)常量),或者所有實(shí)例都能訪問的一個(gè)變量(就像 C 語言中的靜態(tài)變量)。

存儲(chǔ)型類型屬性可以是變量或常量,計(jì)算型類型屬性跟實(shí)例的計(jì)算型屬性一樣只能定義成變量屬性。

方法

實(shí)例方法 (Instance Methods)

實(shí)例方法是屬于某個(gè)特定類、結(jié)構(gòu)體或者枚舉類型實(shí)例的方法。實(shí)例方法提供訪問和修改實(shí)例屬性的方法或提供與實(shí)例目的相關(guān)的功能,并以此來支撐實(shí)例的功能。

class Counter {
    var count = 0
    func increment() {
        count += 1
    }

類型方法

實(shí)例方法是被某個(gè)類型的實(shí)例調(diào)用的方法。你也可以定義在類型本身上調(diào)用的方法,這種方法就叫做類型方法。在方法的func關(guān)鍵字之前加上關(guān)鍵字static,來指定類型方法。類還可以用關(guān)鍵字class來允許子類重寫父類的方法實(shí)現(xiàn)。

class SomeClass {
    class func someTypeMethod() {
        // 在這里實(shí)現(xiàn)類型方法
    }
}
SomeClass.someTypeMethod()

繼承

一個(gè)類可以繼承另一個(gè)類的方法,屬性和其它特性。當(dāng)一個(gè)類繼承其它類時(shí),繼承類叫子類,被繼承類叫超類(或父類)。在 Swift 中,繼承是區(qū)分「類」與其它類型的一個(gè)基本特征。

在 Swift 中,類可以調(diào)用和訪問超類的方法、屬性和下標(biāo),并且可以重寫這些方法,屬性和下標(biāo)來優(yōu)化或修改它們的行為。Swift 會(huì)檢查你的重寫定義在超類中是否有匹配的定義,以此確保你的重寫行為是正確的。

可以為類中繼承來的屬性添加屬性觀察器,這樣一來,當(dāng)屬性值改變時(shí),類就會(huì)被通知到。可以為任何屬性添加屬性觀察器,無論它原本被定義為存儲(chǔ)型屬性還是計(jì)算型屬性。

子類

class SomeClass: SomeSuperclass {
    // 這里是子類的定義
}
class Bicycle: Vehicle {
    var hasBasket = false
}

重寫

子類可以為繼承來的實(shí)例方法,類方法,實(shí)例屬性,或下標(biāo)提供自己定制的實(shí)現(xiàn)。我們把這種行為叫重寫。

如果要重寫某個(gè)特性,你需要在重寫定義的前面加上override關(guān)鍵字。這么做,你就表明了你是想提供一個(gè)重寫版本,而非錯(cuò)誤地提供了一個(gè)相同的定義。意外的重寫行為可能會(huì)導(dǎo)致不可預(yù)知的錯(cuò)誤,任何缺少override關(guān)鍵字的重寫都會(huì)在編譯時(shí)被診斷為錯(cuò)誤。

override關(guān)鍵字會(huì)提醒 Swift 編譯器去檢查該類的超類(或其中一個(gè)父類)是否有匹配重寫版本的聲明。這個(gè)檢查可以確保你的重寫定義是正確的。

訪問超類的方法,屬性及下標(biāo)

當(dāng)你在子類中重寫超類的方法,屬性或下標(biāo)時(shí),有時(shí)在你的重寫版本中使用已經(jīng)存在的超類實(shí)現(xiàn)會(huì)大有裨益。比如,你可以完善已有實(shí)現(xiàn)的行為,或在一個(gè)繼承來的變量中存儲(chǔ)一個(gè)修改過的值。

在合適的地方,你可以通過使用super前綴來訪問超類版本的方法,屬性或下標(biāo):

  • 在方法someMethod()的重寫實(shí)現(xiàn)中,可以通過super.someMethod()來調(diào)用超類版本的someMethod()方法。
  • 在屬性someProperty的 getter 或 setter 的重寫實(shí)現(xiàn)中,可以通過super.someProperty來訪問超類版本的someProperty屬性。
  • 在下標(biāo)的重寫實(shí)現(xiàn)中,可以通過super[someIndex]來訪問超類版本中的相同下標(biāo)。

重寫方法

在子類中,你可以重寫繼承來的實(shí)例方法或類方法,提供一個(gè)定制或替代的方法實(shí)現(xiàn)。

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

重寫屬性

你可以重寫繼承來的實(shí)例屬性或類型屬性,提供自己定制的 getter 和 setter,或添加屬性觀察器使重寫的屬性可以觀察屬性值什么時(shí)候發(fā)生改變。

重寫屬性的 Getters 和 Setters

你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是存儲(chǔ)型的還是計(jì)算型的屬性。子類并不知道繼承來的屬性是存儲(chǔ)型的還是計(jì)算型的,它只知道繼承來的屬性會(huì)有一個(gè)名字和類型。你在重寫一個(gè)屬性時(shí),必需將它的名字和類型都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的。

你可以將一個(gè)繼承來的只讀屬性重寫為一個(gè)讀寫屬性,只需要在重寫版本的屬性里提供 getter 和 setter 即可。但是,你不可以將一個(gè)繼承來的讀寫屬性重寫為一個(gè)只讀屬性。

注意 如果你在重寫屬性中提供了 setter,那么你也一定要提供 getter。如果你不想在重寫版本中的 getter 里修改繼承來的屬性值,你可以直接通過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字。

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

重寫屬性觀察器

你可以通過重寫屬性為一個(gè)繼承來的屬性添加屬性觀察器。這樣一來,當(dāng)繼承來的屬性值發(fā)生改變時(shí),你就會(huì)被通知到,無論那個(gè)屬性原本是如何實(shí)現(xiàn)的。關(guān)于屬性觀察器的更多內(nèi)容,請(qǐng)看屬性觀察器。

注意 你不可以為繼承來的常量存儲(chǔ)型屬性或繼承來的只讀計(jì)算型屬性添加屬性觀察器。這些屬性的值是不可以被設(shè)置的,所以,為它們提供willSet或didSet實(shí)現(xiàn)是不恰當(dāng)。 此外還要注意,你不可以同時(shí)提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化,并且你已經(jīng)為那個(gè)屬性提供了定制的 setter,那么你在 setter 中就可以觀察到任何值變化了。

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

防止重寫

你可以通過把方法,屬性或下標(biāo)標(biāo)記為final來防止它們被重寫,只需要在聲明關(guān)鍵字前加上final修飾符即可(例如:final var,final func,final class func,以及final subscript)。

如果你重寫了帶有final標(biāo)記的方法、屬性或下標(biāo),在編譯時(shí)會(huì)報(bào)錯(cuò)。在類擴(kuò)展中的方法,屬性或下標(biāo)也可以在擴(kuò)展的定義里標(biāo)記為 final 的。

你可以通過在關(guān)鍵字class前添加final修飾符(final class)來將整個(gè)類標(biāo)記為 final 的。這樣的類是不可被繼承的,試圖繼承這樣的類會(huì)導(dǎo)致編譯報(bào)錯(cuò)。

構(gòu)造過程

你可以在構(gòu)造器中為存儲(chǔ)型屬性賦初值,也可以在定義屬性時(shí)為其設(shè)置默認(rèn)值。

構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型的實(shí)例之前的準(zhǔn)備過程。在新實(shí)例可用前必須執(zhí)行這個(gè)過程,具體操作包括設(shè)置實(shí)例中每個(gè)存儲(chǔ)型屬性的初始值和執(zhí)行其他必須的設(shè)置或初始化工作。

通過定義構(gòu)造器來實(shí)現(xiàn)構(gòu)造過程,就像用來創(chuàng)建特定類型新實(shí)例的特殊方法。與 Objective-C 中的構(gòu)造器不同,Swift 的構(gòu)造器無需返回值,它們的主要任務(wù)是保證新實(shí)例在第一次使用前完成正確的初始化。

注:類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。存儲(chǔ)型屬性的值不能處于一個(gè)未知的狀態(tài)。
你可以在構(gòu)造器中為存儲(chǔ)型屬性賦初值,也可以在定義屬性時(shí)為其設(shè)置默認(rèn)值。

構(gòu)造參數(shù)

自定義構(gòu)造過程時(shí),可以在定義中提供構(gòu)造參數(shù),指定參數(shù)值的類型和名字。構(gòu)造參數(shù)的功能和語法跟函數(shù)和方法的參數(shù)相同。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    //不帶外部名的構(gòu)造器參數(shù)
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}

可選屬性類型

如果你定制的類型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性——無論是因?yàn)樗鼰o法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空——你都需要將它定義為可選類型??蛇x類型的屬性將自動(dòng)初始化為 nil,表示這個(gè)屬性是有意在初始化時(shí)設(shè)置為空的。

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
}

默認(rèn)構(gòu)造器

如果結(jié)構(gòu)體或類的所有屬性都有默認(rèn)值,同時(shí)沒有自定義的構(gòu)造器,那么 Swift 會(huì)給這些結(jié)構(gòu)體或類提供一個(gè)默認(rèn)構(gòu)造器(default initializers)。這個(gè)默認(rèn)構(gòu)造器將簡單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為默認(rèn)值的實(shí)例。

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

結(jié)構(gòu)體的逐一成員構(gòu)造器:除了上面提到的默認(rèn)構(gòu)造器,如果結(jié)構(gòu)體沒有提供自定義的構(gòu)造器,它們將自動(dòng)獲得一個(gè)逐一成員構(gòu)造器,即使結(jié)構(gòu)體的存儲(chǔ)型屬性沒有默認(rèn)值。
逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法。我們?cè)谡{(diào)用逐一成員構(gòu)造器時(shí),通過與成員屬性名相同的參數(shù)名進(jìn)行傳值來完成對(duì)成員屬性的初始賦值。

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

指定構(gòu)造器和便利構(gòu)造器

類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值Swift 為類類型提供了兩種構(gòu)造器來確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。

指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類合適的構(gòu)造器來實(shí)現(xiàn)父類的初始化。

類傾向于擁有少量指定構(gòu)造器,普遍的是一個(gè)類擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器在初始化的地方通過“管道”將初始化過程持續(xù)到父類鏈。

每一個(gè)類都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個(gè)條件。具體內(nèi)容請(qǐng)參考后續(xù)章節(jié)構(gòu)造器的自動(dòng)繼承。

便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個(gè)類中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。你也可以定義便利構(gòu)造器來創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。

你應(yīng)當(dāng)只在必要的時(shí)候?yàn)轭愄峁┍憷麡?gòu)造器,比方說某種情況下通過使用便利構(gòu)造器來快捷調(diào)用某個(gè)指定構(gòu)造器,能夠節(jié)省更多開發(fā)時(shí)間并讓類的構(gòu)造過程更清晰明了。

類的構(gòu)造器代理規(guī)則

為了簡化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,Swift 采用以下三條規(guī)則來限制構(gòu)造器之間的代理調(diào)用:

  • 指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。
  • 便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器。
  • 便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。

一個(gè)更方便記憶的方法是:

  • 指定構(gòu)造器必須總是向上代理
  • 便利構(gòu)造器必須總是橫向代理

[圖片上傳失敗...(image-6e68cd-1524110403362)]
如圖所示,父類中包含一個(gè)指定構(gòu)造器和兩個(gè)便利構(gòu)造器。其中一個(gè)便利構(gòu)造器調(diào)用了另外一個(gè)便利構(gòu)造器,而后者又調(diào)用了唯一的指定構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3。這個(gè)父類沒有自己的父類,所以規(guī)則 1 沒有用到。

子類中包含兩個(gè)指定構(gòu)造器和一個(gè)便利構(gòu)造器。便利構(gòu)造器必須調(diào)用兩個(gè)指定構(gòu)造器中的任意一個(gè),因?yàn)樗荒苷{(diào)用同一個(gè)類里的其他構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3。而兩個(gè)指定構(gòu)造器必須調(diào)用父類中唯一的指定構(gòu)造器,這滿足了規(guī)則 1。

[圖片上傳失敗...(image-8256f2-1524110403362)]

兩段式構(gòu)造過程

Swift 中類的構(gòu)造過程包含兩個(gè)階段。第一個(gè)階段,類中的每個(gè)存儲(chǔ)型屬性賦一個(gè)初始值。當(dāng)每個(gè)存儲(chǔ)型屬性的初始值被賦值后,第二階段開始,它給每個(gè)類一次機(jī)會(huì),在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性。

兩段式構(gòu)造過程的使用讓構(gòu)造過程更安全,同時(shí)在整個(gè)類層級(jí)結(jié)構(gòu)中給予了每個(gè)類完全的靈活性。兩段式構(gòu)造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個(gè)構(gòu)造器意外地賦予不同的值。

Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過程不出錯(cuò)地完成:

  • 安全檢查 1

指定構(gòu)造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器。

如上所述,一個(gè)對(duì)象的內(nèi)存只有在其所有存儲(chǔ)型屬性確定之后才能完全初始化。為了滿足這一規(guī)則,指定構(gòu)造器必須保證它所在類的屬性在它往上代理之前先完成初始化。

  • 安全檢查 2

指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類構(gòu)造器,如果沒這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。

  • 安全檢查 3

便利構(gòu)造器必須為任意屬性(包括同類中定義的)賦新值之前代理調(diào)用同一類中的其它構(gòu)造器,如果沒這么做,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋。

  • 安全檢查 4

構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實(shí)例方法,不能讀取任何實(shí)例屬性的值,不能引用self作為一個(gè)值。
類實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有第一階段完成后,該實(shí)例才會(huì)成為有效實(shí)例,才能訪問屬性和調(diào)用方法。

構(gòu)造器的繼承和重寫

跟 Objective-C 中的子類不同,Swift 中的子類默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。Swift 的這種機(jī)制可以防止一個(gè)父類的簡單構(gòu)造器被一個(gè)更精細(xì)的子類繼承,并被錯(cuò)誤地用來創(chuàng)建子類的實(shí)例。

假如你希望自定義的子類中能提供一個(gè)或多個(gè)跟父類相同的構(gòu)造器,你可以在子類中提供這些構(gòu)造器的自定義實(shí)現(xiàn)。

當(dāng)你在編寫一個(gè)和父類中指定構(gòu)造器相匹配的子類構(gòu)造器時(shí),你實(shí)際上是在重寫父類的這個(gè)指定構(gòu)造器。因此,你必須在定義子類構(gòu)造器時(shí)帶上 override 修飾符。即使你重寫的是系統(tǒng)自動(dòng)提供的默認(rèn)構(gòu)造器,也需要帶上 override 修飾符。

正如重寫屬性,方法或者是下標(biāo),override 修飾符會(huì)讓編譯器去檢查父類中是否有相匹配的指定構(gòu)造器,并驗(yàn)證構(gòu)造器參數(shù)是否正確。

構(gòu)造器的自動(dòng)繼承

如上所述,子類在默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動(dòng)繼承的。事實(shí)上,這意味著對(duì)于許多常見場(chǎng)景你不必重寫父類的構(gòu)造器,并且可以在安全的情況下以最小的代價(jià)繼承父類的構(gòu)造器。

假設(shè)你為子類中引入的所有新屬性都提供了默認(rèn)值,以下 2 個(gè)規(guī)則適用:

  • 規(guī)則 1

如果子類沒有定義任何指定構(gòu)造器,它將自動(dòng)繼承父類所有的指定構(gòu)造器。

  • 規(guī)則 2

如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實(shí)現(xiàn)(例如:類本身的便利構(gòu)造器和父類的指定構(gòu)造器重名)——它將自動(dòng)繼承父類所有的便利構(gòu)造器。

即使你在子類中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用。

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}

[圖片上傳失敗...(image-f6c4fd-1524110403362)]

可失敗構(gòu)造器

如果一個(gè)類、結(jié)構(gòu)體或枚舉類型的對(duì)象,在構(gòu)造過程中有可能失敗,則為其定義一個(gè)可失敗構(gòu)造器是很有用的。這里所指的“失敗” 指的是,如給構(gòu)造器傳入無效的參數(shù)值,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。

為了妥善處理這種構(gòu)造過程中可能會(huì)失敗的情況。你可以在一個(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語法為在 init 關(guān)鍵字后面添加問號(hào) (init?)。

可失敗構(gòu)造器會(huì)創(chuàng)建一個(gè)類型為自身類型的可選類型的對(duì)象。你通過 return nil 語句來表明可失敗構(gòu)造器在何種情況下應(yīng)該 “失敗”。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { 
            return nil
        }
        self.species = species
    }
}

let someCreature = Animal(species: "Giraffe")
// someCreature 的類型是 Animal? 而不是 Animal

枚舉類型的可失敗構(gòu)造器

你可以通過一個(gè)帶一個(gè)或多個(gè)參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。如果提供的參數(shù)無法匹配任何枚舉成員,則構(gòu)造失敗。

enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

帶原始值的枚舉類型的可失敗構(gòu)造器

帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:),該可失敗構(gòu)造器有一個(gè)名為 rawValue 的參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個(gè)枚舉成員的原始值匹配,則該構(gòu)造器會(huì)構(gòu)造相應(yīng)的枚舉成員,否則構(gòu)造失敗。

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}

構(gòu)造失敗的傳遞

類,結(jié)構(gòu)體,枚舉的可失敗構(gòu)造器可以橫向代理到同類型中的其他可失敗構(gòu)造器。類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器。

無論是向上代理還是橫向代理,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個(gè)構(gòu)造過程將立即終止,接下來的任何構(gòu)造代碼不會(huì)再被執(zhí)行。

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}

必要構(gòu)造器

在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實(shí)現(xiàn)該構(gòu)造器:

class SomeClass {
    required init() {
        // 構(gòu)造器的實(shí)現(xiàn)代碼
    }
}

在子類重寫父類的必要構(gòu)造器時(shí),必須在子類的構(gòu)造器前也添加 required 修飾符,表明該構(gòu)造器要求也應(yīng)用于繼承鏈后面的子類。在重寫父類中必要的指定構(gòu)造器時(shí),不需要添加 override 修飾符:

class SomeSubclass: SomeClass {
    required init() {
        // 構(gòu)造器的實(shí)現(xiàn)代碼
    }
}

通過閉包或函數(shù)設(shè)置屬性的默認(rèn)值

如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要一些定制或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值。每當(dāng)某個(gè)屬性所在類型的新實(shí)例被創(chuàng)建時(shí),對(duì)應(yīng)的閉包或函數(shù)會(huì)被調(diào)用,而它們的返回值會(huì)當(dāng)做默認(rèn)值賦值給這個(gè)屬性。

這種類型的閉包或函數(shù)通常會(huì)創(chuàng)建一個(gè)跟屬性類型相同的臨時(shí)變量,然后修改它的值以滿足預(yù)期的初始狀態(tài),最后返回這個(gè)臨時(shí)變量,作為屬性的默認(rèn)值。

class SomeClass {
    let someProperty: SomeType = {
        // 在這個(gè)閉包中給 someProperty 創(chuàng)建一個(gè)默認(rèn)值
        // someValue 必須和 SomeType 類型相同
        return someValue
    }()
}

相對(duì)于計(jì)算屬性每次返回相同一樣的值,閉包只是一次性賦值,后續(xù)可更改

可選鏈?zhǔn)秸{(dià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)用于任意類型,并且能檢查調(diào)用是否成功。

使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開

通過在想調(diào)用的屬性、方法、或下標(biāo)的可選值后面放一個(gè)問號(hào)(?),可以定義一個(gè)可選鏈。這一點(diǎn)很像在可選值后面放一個(gè)嘆號(hào)(!)來強(qiáng)制展開它的值。它們的主要區(qū)別在于當(dāng)可選值為空時(shí)可選鏈?zhǔn)秸{(diào)用只會(huì)調(diào)用失敗,然而強(qiáng)制展開將會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類型,但是被包裝成了一個(gè)可選值。例如,使用可選鏈?zhǔn)秸{(diào)用訪問屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時(shí),如果屬性原本的返回結(jié)果是Int類型,則會(huì)變?yōu)镮nt?類型。

  • 通過可選鏈?zhǔn)秸{(diào)用訪問屬性
    正如使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開中所述,可以通過可選鏈?zhǔn)秸{(diào)用在一個(gè)可選值上訪問它的屬性,并判斷訪問是否成功。

  • 通過可選鏈?zhǔn)秸{(diào)用調(diào)用方法
    可以通過可選鏈?zhǔn)秸{(diào)用來調(diào)用方法,并判斷是否調(diào)用成功,即使這個(gè)方法沒有返回值。

func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}

這個(gè)方法沒有返回值。然而,沒有返回值的方法具有隱式的返回類型Void,如無返回值函數(shù)中所述。這意味著沒有返回值的方法也會(huì)返回(),或者說空的元組。

如果在可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用這個(gè)方法,該方法的返回類型會(huì)是Void?,而不是Void,因?yàn)橥ㄟ^可選鏈?zhǔn)秸{(diào)用得到的返回值都是可選的。這樣我們就可以使用if語句來判斷能否成功調(diào)用printNumberOfRooms()方法,即使方法本身沒有定義返回值。通過判斷返回值是否為nil可以判斷調(diào)用是否成功:

  • 通過可選鏈?zhǔn)秸{(diào)用訪問下標(biāo)
    通過可選鏈?zhǔn)秸{(diào)用,我們可以在一個(gè)可選值上訪問下標(biāo),并且判斷下標(biāo)調(diào)用是否成功。

連接多層可選鏈?zhǔn)秸{(diào)用
可以通過連接多個(gè)可選鏈?zhǔn)秸{(diào)用在更深的模型層級(jí)中訪問屬性、方法以及下標(biāo)。然而,多層可選鏈?zhǔn)秸{(diào)用不會(huì)增加返回值的可選層級(jí)。

也就是說:

  • 如果你訪問的值不是可選的,可選鏈?zhǔn)秸{(diào)用將會(huì)返回可選值。
  • 如果你訪問的值就是可選的,可選鏈?zhǔn)秸{(diào)用不會(huì)讓可選返回值變得“更可選”。
    因此:
  • 通過可選鏈?zhǔn)秸{(diào)用訪問一個(gè)Int值,將會(huì)返回Int?,無論使用了多少層可選鏈?zhǔn)秸{(diào)用。
  • 類似的,通過可選鏈?zhǔn)秸{(diào)用訪問Int?值,依舊會(huì)返回Int?值,并不會(huì)返回Int??。

在方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用
我們可以在一個(gè)可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用方法,并且可以根據(jù)需要繼續(xù)在方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
        if beginsWithThe {
            print("John's building identifier begins with \"The\".")
        } else {
            print("John's building identifier does not begin with \"The\".")
        }
}
// 打印 “John's building identifier begins with "The".”

錯(cuò)誤處理

錯(cuò)誤處理(Error handling)是響應(yīng)錯(cuò)誤以及從錯(cuò)誤中恢復(fù)的過程。Swift 提供了在運(yùn)行時(shí)對(duì)可恢復(fù)錯(cuò)誤的拋出、捕獲、傳遞和操作的一等公民支持。

表示并拋出錯(cuò)誤

在 Swift 中,錯(cuò)誤用符合Error協(xié)議的類型的值來表示。這個(gè)空協(xié)議表明該類型可以用于錯(cuò)誤處理。

Swift 的枚舉類型尤為適合構(gòu)建一組相關(guān)的錯(cuò)誤狀態(tài),枚舉的關(guān)聯(lián)值還可以提供錯(cuò)誤狀態(tài)的額外信息。例如,你可以這樣表示在一個(gè)游戲中操作自動(dòng)販賣機(jī)時(shí)可能會(huì)出現(xiàn)的錯(cuò)誤狀態(tài):

enum VendingMachineError: Error {
    case invalidSelection                    //選擇無效
    case insufficientFunds(coinsNeeded: Int) //金額不足
    case outOfStock                          //缺貨
}

拋出一個(gè)錯(cuò)誤可以讓你表明有意外情況發(fā)生,導(dǎo)致正常的執(zhí)行流程無法繼續(xù)執(zhí)行。拋出錯(cuò)誤使用throw關(guān)鍵字。例如,下面的代碼拋出一個(gè)錯(cuò)誤,提示販賣機(jī)還需要5個(gè)硬幣:

throw VendingMachineError. insufficientFunds(coinsNeeded: 5)

用 throwing 函數(shù)傳遞錯(cuò)誤

為了表示一個(gè)函數(shù)、方法或構(gòu)造器可以拋出錯(cuò)誤,在函數(shù)聲明的參數(shù)列表之后加上throws關(guān)鍵字。一個(gè)標(biāo)有throws關(guān)鍵字的函數(shù)被稱作throwing 函數(shù)。如果這個(gè)函數(shù)指明了返回值類型,throws關(guān)鍵詞需要寫在箭頭(->)的前面。

func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

用 Do-Catch 處理錯(cuò)誤

可以使用一個(gè)do-catch語句運(yùn)行一段閉包代碼來處理錯(cuò)誤。如果在do子句中的代碼拋出了一個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤會(huì)與catch子句做匹配,從而決定哪條子句能處理它。

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}

指定清理操作

可以使用defer語句在即將離開當(dāng)前代碼塊時(shí)執(zhí)行一系列語句。該語句讓你能執(zhí)行一些必要的清理工作,不管是以何種方式離開當(dāng)前代碼塊的——無論是由于拋出錯(cuò)誤而離開,或是由于諸如return、break的語句。例如,你可以用defer語句來確保文件描述符得以關(guān)閉,以及手動(dòng)分配的內(nèi)存得以釋放。

defer語句將代碼的執(zhí)行延遲到當(dāng)前的作用域退出之前。該語句由defer關(guān)鍵字和要被延遲執(zhí)行的語句組成。延遲執(zhí)行的語句不能包含任何控制轉(zhuǎn)移語句,例如break、return語句,或是拋出一個(gè)錯(cuò)誤。延遲執(zhí)行的操作會(huì)按照它們聲明的順序從后往前執(zhí)行——也就是說,第一條defer語句中的代碼最后才執(zhí)行,第二條defer語句中的代碼倒數(shù)第二個(gè)執(zhí)行,以此類推。最后一條語句會(huì)第一個(gè)執(zhí)行

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)用,即作用域的最后。
    }
}

類型轉(zhuǎn)換

類型轉(zhuǎn)換 可以判斷實(shí)例的類型,也可以將實(shí)例看做是其父類或者子類的實(shí)例。

類型轉(zhuǎn)換在 Swift 中使用 is 和 as 操作符實(shí)現(xiàn)。這兩個(gè)操作符提供了一種簡單達(dá)意的方式去檢查值的類型或者轉(zhuǎn)換它的類型。

檢查類型

用類型檢查操作符(is)來檢查一個(gè)實(shí)例是否屬于特定子類型。若實(shí)例屬于那個(gè)子類型,類型檢查操作符返回 true,否則返回 false。

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs”

向下轉(zhuǎn)型

某類型的一個(gè)常量或變量可能在幕后實(shí)際上屬于一個(gè)子類。當(dāng)確定是這種情況時(shí),你可以嘗試向下轉(zhuǎn)到它的子類型,用類型轉(zhuǎn)換操作符(as? 或 as!)。

因?yàn)橄蛳罗D(zhuǎn)型可能會(huì)失敗,類型轉(zhuǎn)型操作符帶有兩種不同形式。條件形式as? 返回一個(gè)你試圖向下轉(zhuǎn)成的類型的可選值。強(qiáng)制形式 as! 把試圖向下轉(zhuǎn)型和強(qiáng)制解包轉(zhuǎn)換結(jié)果結(jié)合為一個(gè)操作。

當(dāng)你不確定向下轉(zhuǎn)型可以成功時(shí),用類型轉(zhuǎn)換的條件形式(as?)。條件形式的類型轉(zhuǎn)換總是返回一個(gè)可選值,并且若下轉(zhuǎn)是不可能的,可選值將是 nil。這使你能夠檢查向下轉(zhuǎn)型是否成功。

只有你可以確定向下轉(zhuǎn)型一定會(huì)成功時(shí),才使用強(qiáng)制形式(as!)。當(dāng)你試圖向下轉(zhuǎn)型為一個(gè)不正確的類型時(shí),強(qiáng)制形式的類型轉(zhuǎn)換會(huì)觸發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤

for item in library {
    if let movie = item as? Movie {
        print("Movie: '\(movie.name)', dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: '\(song.name)', by \(song.artist)")
    }
}

// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley

Any 和 AnyObject 的類型轉(zhuǎn)換

var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

things 數(shù)組包含兩個(gè) Int 值,兩個(gè) Double 值,一個(gè) String 值,一個(gè)元組 (Double, Double),一個(gè)Movie實(shí)例“Ghostbusters”,以及一個(gè)接受 String 值并返回另一個(gè) String 值的閉包表達(dá)式。

你可以在 switch 表達(dá)式的 case 中使用 is 和 as 操作符來找出只知道是 Any 或 AnyObject 類型的常量或變量的具體類型。下面的示例迭代 things 數(shù)組中的每一項(xiàng),并用 switch 語句查找每一項(xiàng)的類型。有幾個(gè) switch 語句的 case 綁定它們匹配到的值到一個(gè)指定類型的常量,從而可以打印這些值:

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        print("a movie called '\(movie.name)', dir. \(movie.director)")
    case let stringConverter as String -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
// Hello, Michael

擴(kuò)展(Extensions)

擴(kuò)展 就是為一個(gè)已有的類、結(jié)構(gòu)體、枚舉類型或者協(xié)議類型添加新功能。這包括在沒有權(quán)限獲取原始源代碼的情況下擴(kuò)展類型的能力(即 逆向建模 )。擴(kuò)展和 Objective-C 中的分類類似。(與 Objective-C 不同的是,Swift 的擴(kuò)展沒有名字。)

Swift 中的擴(kuò)展可以:

  • 添加計(jì)算型屬性和計(jì)算型類型屬性
  • 定義實(shí)例方法和類型方法
  • 提供新的構(gòu)造器
  • 定義下標(biāo)
  • 定義和使用新的嵌套類型
  • 使一個(gè)已有類型符合某個(gè)協(xié)議

擴(kuò)展語法

使用關(guān)鍵字 extension 來聲明擴(kuò)展:

extension SomeType {
    // 為 SomeType 添加的新功能寫到這里
}

extension SomeType: SomeProtocol, AnotherProctocol {
    // 協(xié)議實(shí)現(xiàn)寫到這里
}

可變實(shí)例方法

通過擴(kuò)展添加的實(shí)例方法也可以修改該實(shí)例本身。結(jié)構(gòu)體和枚舉類型中修改 self 或其屬性的方法必須將該實(shí)例方法標(biāo)注為 mutating,正如來自原始實(shí)現(xiàn)的可變方法一樣。

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt 的值現(xiàn)在是 9

協(xié)議

協(xié)議 定義了一個(gè)藍(lán)圖,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法、屬性,以及其他需要的東西。類、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議,并為協(xié)議定義的這些要求提供具體實(shí)現(xiàn)。某個(gè)類型能夠滿足某個(gè)協(xié)議的要求,就可以說該類型遵循這個(gè)協(xié)議。

協(xié)議語法

協(xié)議的定義方式與類、結(jié)構(gòu)體和枚舉的定義非常相似:

protocol SomeProtocol {
    // 這里是協(xié)議的定義部分
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 這里是結(jié)構(gòu)體的定義部分
}

擁有父類的類在遵循協(xié)議時(shí),應(yīng)該將父類名放在協(xié)議名之前,以逗號(hào)分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 這里是類的定義部分
}

屬性要求

協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實(shí)例屬性或類型屬性。協(xié)議不指定屬性是存儲(chǔ)型屬性還是計(jì)算型屬性,它只指定屬性的名稱和類型。此外,協(xié)議還指定屬性是可讀的還是可讀可寫的。

如果協(xié)議要求屬性是可讀可寫的,那么該屬性不能是常量屬性或只讀的計(jì)算型屬性。如果協(xié)議只要求屬性是可讀的,那么該屬性不僅可以是可讀的,如果代碼需要的話,還可以是可寫的。

協(xié)議總是用 var 關(guān)鍵字來聲明變量屬性,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的,可讀屬性則用 { get } 來表示:

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

在協(xié)議中定義類型屬性時(shí),總是使用 static 關(guān)鍵字作為前綴。當(dāng)類類型遵循協(xié)議時(shí),除了 static 關(guān)鍵字,還可以使用 class 關(guān)鍵字來聲明類型屬性:

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

方法要求

協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)某些指定的實(shí)例方法或類方法。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中,但是不需要大括號(hào)和方法體。可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同。但是,不支持為協(xié)議中的方法的參數(shù)提供默認(rèn)值。

正如屬性要求中所述,在協(xié)議中定義類方法的時(shí)候,總是使用 static 關(guān)鍵字作為前綴。當(dāng)類類型遵循協(xié)議時(shí),除了 static 關(guān)鍵字,還可以使用 class 關(guān)鍵字作為前綴:

protocol SomeProtocol {
    static func someTypeMethod()
}

實(shí)例方法:

protocol RandomNumberGenerator {
    func random() -> Double
}

Mutating 方法要求

有時(shí)需要在方法中改變方法所屬的實(shí)例。例如,在值類型(即結(jié)構(gòu)體和枚舉)的實(shí)例方法中,將 mutating 關(guān)鍵字作為方法的前綴,寫在 func 關(guān)鍵字之前,表示可以在該方法中修改它所屬的實(shí)例以及實(shí)例的任意屬性的值。這一過程在在實(shí)例方法中修改值類型章節(jié)中有詳細(xì)描述。

如果你在協(xié)議中定義了一個(gè)實(shí)例方法,該方法會(huì)改變遵循該協(xié)議的類型的實(shí)例,那么在定義協(xié)議時(shí)需要在方法前加 mutating 關(guān)鍵字。這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求。

enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}

構(gòu)造器要求

協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)指定的構(gòu)造器。你可以像編寫普通構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號(hào)和構(gòu)造器的實(shí)體:

protocol SomeProtocol {
    init(someParameter: Int)
}
class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 這里是構(gòu)造器的實(shí)現(xiàn)部分
    }
}

如果一個(gè)子類重寫了父類的指定構(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)槔^承自父類,需要加上 override
    required override init() {
        // 這里是構(gòu)造器的實(shí)現(xiàn)部分
    }
}

協(xié)議作為類型

盡管協(xié)議本身并未實(shí)現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做一個(gè)成熟的類型來使用。

協(xié)議可以像其他普通類型一樣使用,使用場(chǎng)景如下:

  • 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
  • 作為常量、變量或?qū)傩缘念愋?/li>
  • 作為數(shù)組、字典或其他容器中的元素類型
class LinearCongruentialGenerator : RandomNumberGenerator{
}

class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> RandomNumberGenerator {
        return LinearCongruentialGenerator()
    }
}

委托(代理)模式

委托是一種設(shè)計(jì)模式,它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實(shí)例。委托模式的實(shí)現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能,這樣就能確保遵循協(xié)議的類型能提供這些功能。委托模式可以用來響應(yīng)特定的動(dòng)作,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無需關(guān)心外部數(shù)據(jù)源的類型。

protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}

通過擴(kuò)展添加協(xié)議一致性

即便無法修改源代碼,依然可以通過擴(kuò)展令已有類型遵循并符合協(xié)議。擴(kuò)展可以為已有類型添加屬性、方法、下標(biāo)以及構(gòu)造器,因此可以符合協(xié)議中的相應(yīng)要求。

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

通過擴(kuò)展遵循協(xié)議

當(dāng)一個(gè)類型已經(jīng)符合了某個(gè)協(xié)議中的所有要求,卻還沒有聲明遵循該協(xié)議時(shí),可以通過空擴(kuò)展體的擴(kuò)展來遵循該協(xié)議:

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
//從現(xiàn)在起,Hamster 的實(shí)例可以作為 TextRepresentable 類型使用:

協(xié)議類型的集合

協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用,在協(xié)議類型提到了這樣的用法。下面的例子創(chuàng)建了一個(gè)元素類型為 TextRepresentable 的數(shù)組:

let things: [TextRepresentable] = [game, d12, simonTheHamster]

協(xié)議的繼承

協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。協(xié)議的繼承語法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 這里是協(xié)議的定義部分
}

類類型專屬協(xié)議

你可以在協(xié)議的繼承列表中,通過添加 class 關(guān)鍵字來限制協(xié)議只能被類類型遵循,而結(jié)構(gòu)體或枚舉不能遵循該協(xié)議。class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 這里是類類型專屬協(xié)議的定義部分
}

協(xié)議合成

有時(shí)候需要同時(shí)遵循多個(gè)協(xié)議,你可以將多個(gè)協(xié)議采用 SomeProtocol & AnotherProtocol 這樣的格式進(jìn)行組合,稱為 協(xié)議合成(protocol composition)。你可以羅列任意多個(gè)你想要遵循的協(xié)議,以與符號(hào)(&)分隔。

下面的例子中,將 Named 和 Aged 兩個(gè)協(xié)議按照上述語法組合成一個(gè)協(xié)議,作為函數(shù)參數(shù)的類型:

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)
// 打印 “Happy birthday Malcolm - you're 21!”

檢查協(xié)議一致性

你可以使用類型轉(zhuǎn)換中描述的 is 和 as 操作符來檢查協(xié)議一致性,即是否符合某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型。檢查和轉(zhuǎn)換到某個(gè)協(xié)議類型在語法上和類型的檢查和轉(zhuǎn)換完全相同:

  • is 用來檢查實(shí)例是否符合某個(gè)協(xié)議,若符合則返回 true,否則返回 false。
  • as? 返回一個(gè)可選值,當(dāng)實(shí)例符合某個(gè)協(xié)議時(shí),返回類型為協(xié)議類型的可選值,否則返回 nil。
  • as! 將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個(gè)協(xié)議類型,如果強(qiáng)轉(zhuǎn)失敗,會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。

可選的協(xié)議要求

協(xié)議可以定義可選要求,遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求。在協(xié)議中使用 optional 關(guān)鍵字作為前綴來定義可選要求??蛇x要求用在你需要和 Objective-C 打交道的代碼中。協(xié)議和可選要求都必須帶上@objc屬性。標(biāo)記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 @objc 類遵循,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議。

使用可選要求時(shí)(例如,可選的方法或者屬性),它們的類型會(huì)自動(dòng)變成可選的。比如,一個(gè)類型為 (Int) -> String 的方法會(huì)變成 ((Int) -> String)?。需要注意的是整個(gè)函數(shù)類型是可選的,而不是函數(shù)的返回值。

協(xié)議中的可選要求可通過可選鏈?zhǔn)秸{(diào)用來使用,因?yàn)樽裱瓍f(xié)議的類型可能沒有實(shí)現(xiàn)這些可選要求。類似 someOptionalMethod?(someArgument) 這樣,你可以在可選方法名稱后加上 ? 來調(diào)用可選方法。

協(xié)議擴(kuò)展

協(xié)議可以通過擴(kuò)展來為遵循協(xié)議的類型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)。通過這種方式,你可以基于協(xié)議本身來實(shí)現(xiàn)這些功能,而無需在每個(gè)遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn),也無需使用全局函數(shù)。

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

提供默認(rèn)實(shí)現(xiàn)

可以通過協(xié)議擴(kuò)展來為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)。如果遵循協(xié)議的類型為這些要求提供了自己的實(shí)現(xiàn),那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用。

通過協(xié)議擴(kuò)展為協(xié)議要求提供的默認(rèn)實(shí)現(xiàn)和可選的協(xié)議要求不同。雖然在這兩種情況下,遵循協(xié)議的類型都無需自己實(shí)現(xiàn)這些要求,但是通過擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)可以直接調(diào)用,而無需使用可選鏈?zhǔn)秸{(diào)用。

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

為協(xié)議擴(kuò)展添加限制條件

在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制條件,只有遵循協(xié)議的類型滿足這些限制條件時(shí),才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。這些限制條件寫在協(xié)議名之后,使用 where 子句來描述,正如Where子句中所描述的。

extension Collection where Iterator.Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

如果多個(gè)協(xié)議擴(kuò)展都為同一個(gè)協(xié)議要求提供了默認(rèn)實(shí)現(xiàn),而遵循協(xié)議的類型又同時(shí)滿足這些協(xié)議擴(kuò)展的限制條件,那么將會(huì)使用限制條件最多的那個(gè)協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。

參考鏈接:
1.極客學(xué)院-The Swift Programming Language 中文版
2.蘋果官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 swift是類型安全語言1、在 Swift 中,所有的基本類型:整數(shù)(Integer)、浮點(diǎn)數(shù)(floatin...
    白河三閱讀 619評(píng)論 0 1
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 4,169評(píng)論 1 10
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 常量與變量使用let來聲明常量,使用var來聲明變量。聲明的同時(shí)賦值的話,編譯器會(huì)自動(dòng)推斷類型。值永遠(yuǎn)不會(huì)被隱式轉(zhuǎn)...
    莫_名閱讀 516評(píng)論 0 1
  • 從今天開始,我將選擇不再逃避, 我要勇敢的面對(duì)我的生活,不再逃避,我從小生活在沒有父愛沒有母愛的環(huán)境中,生活在沒有...
    魅狐閱讀 207評(píng)論 0 0

友情鏈接更多精彩內(nèi)容