Swift5.2-開篇(中文文檔)

引言

今天,開始系統學習Swift,之前都是零零散散的看看的let和var的區別、泛型,只知道它是一個面向協議且類型安全的語言,性能比OC好多了,也沒有去實踐過,感受很空虛(心裏感覺,不要想歪)!開發了那麼久纔開始加入Swift,感受落後了好多(T_T)... 好了,往事不提,從今天開始把它撿起來就行了。以爲對於我而言,最有效的學習方法是:一邊學習官方文檔,一邊來翻譯一遍,印象會更深入一點(好吧,我認可,這是最沒效率的一種方式T_T,奈何個人記性不是很好,只能用這種方法了)。在學習過程當中,也終於搞清楚了Swift裏特有的一些功能的用法,好比元組可選值和解包:?和!泛型If let和If var等等,說明學習一門語言,還要作從官方文檔開始(我的看法,不喜勿噴)!接下來,讓咱們一塊兒去探索Swift的奇妙之處吧!Fighting~html

若是你已經大概瞭解Swift的基本知識點,那麼請參閱下一章節:基礎程序員

歡迎來到SWIFT

1 關於Swift

Swift是一個編寫軟件的好方式,不管是手機、桌面、服務器仍是其餘運行代碼的軟件。它是一種安全、快速、交互性強的編程語言,融合了現代語言思惟的精華和蘋果工程文化的智慧,以及蘋果開源社區的各類貢獻。編譯器針對性能進行了優化,語言針對開發進行了優化,二者都沒有妥協。編程

Swift對新程序員很友好。它是一種工業質量的編程語言,與腳本語言同樣具備表現力和趣味性。在playground上編寫Swift代碼可讓你體驗代碼並當即看到結果,而不用承擔構建和運行應用程序的開銷。swift

Swift經過採用現代編程模式定義了大量的常見編程錯誤:數組

  • 變量在使用以前老是要初始化。
  • 檢查數組索引是否有越界錯誤。
  • 檢查整數是否溢出。
  • 可選的變量確保顯式地處理nil值。
  • 自動管理內存
  • 錯誤處理容許從意外錯誤中控制恢復。

Swift代碼是編譯和優化過的,以得到最大限度的現代硬件。語法和標準庫是基於這樣的指導原則設計的:顯而易見的編寫代碼的方法也應該具備最好的性能。它的安全和速度的結合使Swift成爲一個優秀的選擇,從「Hello, world!」到整個操做系統。安全

Swift將強大的類型推斷和模式匹配與現代的輕量級語法結合在一塊兒,容許以清晰而簡潔的方式表達複雜的思想。所以,代碼不只更容易編寫,並且更容易閱讀和維護。bash

Swift已經醞釀了數年,並不斷髮展新的特性和功能。咱們對Swift的目標是雄心勃勃的。咱們火燒眉毛地想看看你用它創造了什麼。服務器

2 版本兼容性

本書描述了Xcode 11.4中包含的Swift默認版本Swift 5.2。您可使用Xcode 11.4來構建用Swift 5.二、Swift 4.2或Swift 4來編寫targets。閉包

當你用Xcode11.4構建Swift 4和Swift 4.2代碼時,大多數Swift 5.2的功能也能夠用。也就是說,如下更改僅適用於使用Swift 5.2或更高版本的代碼:app

  • 返回不透明類型的函數須要Swift 5.1運行時。
  • 試一試?表達式沒有給已經返回可選的表達式引入額外的可選的級別。
  • 大整數文字的初始化表達式被推斷爲正確的整數類型。例如,UInt64(0xffff_ffff_ffff_ffff)會計算出正確的值,而不是溢出。

用Swift 5.2編寫的target能夠依賴於Swift 4.2或Swift 4編寫的target,反之亦然。這意味着,若是您有一個被劃分爲多個框架的大型項目,您能夠一次將代碼從Swift 4遷移到Swift 5.2。

3 Swift之旅

根據傳統,用一種新語言編寫的第一個程序應該打印「Hello, world!」「在屏幕上。在Swift中,這能夠在一行中完成:

print("Hello, world!")
// Prints "Hello, world!"
複製代碼

若是你用C或Objective-C編寫代碼,這種語法對你來講很熟悉——在Swift中,這行代碼是一個完整的程序。對於輸入/輸出或字符串處理等功能,不須要導入單獨的庫。在全局做用域編寫的代碼用做程序的入口點,所以不須要main()函數。您也不須要在每條語句的末尾寫上分號。

經過展現如何完成各類編程任務,本指南爲您提供了足夠的信息來開始使用Swift編寫代碼。若是你有不懂的地方,也不用擔憂——本書後面的部分會詳細解釋這一旅程中介紹的全部內容。

請注意 爲了得到最好的體驗,在Xcode中將本章做爲playground打開。Playgrounds容許您編輯代碼並當即看到結果。 Download Playground

3.1 簡單的值

使用let建立常量,使用var建立變量。一個常量的值不須要在編譯時被知道,可是必須精確地一次爲它賦值。這意味着您可使用常量來命名一次肯定但在許多地方使用的值。

var myVariable = 42
myVariable = 50
let myConstant = 42
複製代碼

常量或變量的類型必須與要賦值的類型相同。可是,沒必要老是顯式地編寫類型。在建立常量或變量時提供一個值,使編譯器能夠推斷其類型。在上面的例子中,編譯器推斷myVariable是一個整數,由於它的初始值是一個整數。

若是初始值沒有提供足夠的信息(或者沒有初始值),經過將其寫入變量後面,用冒號分隔,指定類型。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
複製代碼

實驗 建立一個具備顯式Float類型和值4的常量。

值永遠不會隱式轉換爲另外一種類型。若是須要將一個值轉換爲另外一種類型,請顯式地建立所需類型的實例。

let label = "The width is "
let width = 50
let labelWidth = label + String(width)
複製代碼

實驗 嘗試從最後一行刪除轉換字符串。偏差是多少?

在字符串中包含值還有一種更簡單的方法:將值寫在圓括號中,而後在圓括號前寫一個反斜槓()。例如:

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples. "
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
複製代碼

實驗 使用\ ()在字符串中包含浮點計算,並在問候語中包含某人的名字。

對於佔用多個行的字符串,使用三個雙引號(""")。只要它與結束引號的縮進一致,就能夠刪除每一個引用行開始的縮進。例如:

let quotation = """ I said "I have \(apples) apples." And then I said "I have \(apples + oranges) pieces of fruit." """
複製代碼

使用方括號([])建立數組和字典,並經過在方括號中寫入索引或鍵來訪問它們的元素。最後一個元素後面容許有逗號。

var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
複製代碼

數組會隨着添加元素而自動增加。

shoppingList.append("blue paint")
print(shoppingList)
複製代碼

要建立空數組或字典,請使用初始化語法。

let emptyArray = [String]()
let emptyDictionary = [String: Float]()
複製代碼

若是能夠推斷類型信息,則能夠將空數組寫成[],將空字典寫成[:]——例如,在爲變量設置新值或向函數傳遞參數時。

shoppingList = []
occupations = [:]
複製代碼

3.2 控制流

使用if和switch來生成條件語句,使用for-in、while和repeat-while來生成循環。條件或循環變量周圍的圓括號是可選的。須要在body周圍帶支架。

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)
// Prints "11"
複製代碼

在if語句中,條件語句必須是布爾表達式——這意味着代碼如if score{…}是一個錯誤,而不是隱含的對零的比較。

可使用if和let一塊兒處理可能丟失的值。這些值表示爲可選。可選值要麼包含值,要麼包含nil,以表示缺乏值。在值的類型後面寫一個問號(?)來標記該值爲可選值。

var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
複製代碼

實驗 更改optionalName爲nil。你收到了什麼問候?若是optionalName爲nil,添加一個else子句來設置不一樣的問候語。

若是可選值爲nil,則條件爲false,並跳過大括號中的代碼。不然,可選值將被解包並賦給let以後的常量,這使得解包值在代碼塊中可用。

處理可選值的另外一種方法是使用??操做符。若是缺乏可選值,則使用默認值。

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
複製代碼

Switches支持任何類型的數據和各類比較操做—它們不限於整數和是否相等的測試。

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"
複製代碼

實驗 移除default看看會有這麼錯誤

結果:會報錯補充:在枚舉裏,沒有default又不會報錯`

請注意在模式中如何使用let將與模式匹配的值分配給一個常量。

在執行匹配的switch case中的代碼以後,程序從switch語句中退出。執行不會繼續到下一個case,所以不須要在每一個case的代碼末尾顯式地中斷switch。

經過爲每一個鍵值對提供一對名稱,可使用for-in迭代字典中的項。字典是一個無序的集合,所以它們的鍵和值將以任意順序迭代。

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// Prints "25"
複製代碼

實驗 添加另外一個變量來跟蹤哪一種數字是最大的,以及最大的數字是什麼。

使用while重複代碼塊,直到條件發生變化。循環的條件能夠放在末尾,以確保循環至少運行一次。

var n = 2
while n < 100 {
    n *= 2
}
print(n)
// Prints "128"

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)
// Prints "128"
複製代碼

您可使用..在循環中保存索引,以生成索引範圍。

var total = 0
for i in 0..<4 {
    total += i
}
print(total)
// Prints "6"
複製代碼

使用. .<建立一個忽略其上值的範圍,並使用…生成包含這兩個值的範圍。

var total = 0
for i in 0...4 {
    total += i
}
print(total)
// Prints "10"
複製代碼

3.3 函數和閉包

使用func來聲明一個函數。經過在函數名後面加上圓括號中的參數列表來調用函數。使用->將參數名稱和類型與函數的返回類型分開。

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
複製代碼

實驗 刪除day參數。添加一個參數,將今天的特別午飯包含在問候中。

默認狀況下,函數使用它們的參數名做爲參數的標籤。在參數名前寫一個自定義參數標籤,或寫_來不使用參數標籤。

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
複製代碼

使用元組生成複合值——例如,從函數返回多個值。元組的元素能夠經過名稱或數字引用。

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"
複製代碼

函數能夠嵌套。嵌套函數能夠訪問在外部函數中聲明的變量。可使用嵌套函數將代碼組織到一個長或複雜的函數中。

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()
複製代碼

函數是一類類型。這意味着一個函數能夠返回另外一個函數做爲它的值。

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)
複製代碼

一個函數能夠接受另外一個函數做爲它的參數之一。

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
複製代碼

函數其實是閉包的一種特殊狀況:後面能夠調用的代碼塊。閉包中的代碼能夠訪問在建立閉包的做用域中可用的變量和函數,即便閉包在執行時處於不一樣的做用域中—您已經在嵌套函數中看到了這樣的示例。經過在代碼周圍使用大括號({}),能夠編寫沒有名稱的閉包。用於將參數和返回類型與主體分隔開。

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
複製代碼

實驗 重寫閉包以對全部奇數返回零。

numbers.map({ (number: Int) -> Int in
     if number % 2 != 0 {
          return 0
     }
     return number
})
複製代碼

有幾種方法能夠更簡潔地編寫閉包。若是已經知道閉包的類型(好比委託的回調),則能夠省略其參數的類型、返回類型或二者都省略。單語句閉包隱式地返回它們惟一語句的值。

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"
複製代碼

能夠經過數字而不是名稱引用參數——這種方法在很是短的閉包中特別有用。做爲函數最後一個參數傳遞的閉包能夠當即出如今括號以後。當閉包是函數的惟一參數時,能夠徹底省略括號。

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"
複製代碼

3.4 對象和類

使用類後跟類名建立類。類中的屬性聲明與常量或變量聲明的編寫方式相同,只是它是在類的上下文中。一樣,方法和函數聲明也以一樣的方式編寫。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
複製代碼

實驗 用let添加一個常量屬性,並添加另外一個接受參數的方法。

經過在類名後面加上括號來建立類的實例。使用點語法訪問實例的屬性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
複製代碼

這個版本的Shape類缺乏一些重要的東西:建立實例時用於設置類的初始化器。使用init建立一個。

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
複製代碼

請注意如何使用self來區分name屬性和初始化器的name參數。在建立類的實例時,初始化器的參數像函數調用同樣傳遞。每一個屬性都須要分配一個值——要麼在其聲明中(如numberOfSides),要麼在初始化器中(如name)。

若是須要在釋放對象以前執行一些清理,請使用deinit建立一個deinitializer。

子類包括它們的父類名在它們的類名以後,用冒號分隔。類不須要子類化任何標準根類,所以能夠根據須要包含或省略父類。

重寫父類實現的子類上的方法標記爲重寫—意外重寫方法,若是不重寫,編譯器將檢測爲錯誤。編譯器還會檢測帶有override的方法,這些方法實際上沒有覆蓋父類中的任何方法。

class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
複製代碼

實驗 建立NamedShape的另外一個子類Circle,它接受半徑和名稱做爲初始化器的參數。在Circle類上實現area()和simpleDescription()方法。

除了存儲的簡單屬性外,屬性還能夠有getter和setter。

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"
複製代碼

在setter中的perimeter,新值的隱式名稱爲newValue。能夠在set後面的圓括號中提供顯式名稱。如:

set(value) {  //setter的顯式名稱value
   sideLength = value / 3.0
}
複製代碼

注意EquilateralTriangle類的初始化器有三個不一樣的步驟:

  1. 設置子類聲明的屬性的值。
  2. 調用父類的初始化方法。
  3. 更改父類定義的屬性的值。使用方法、getter或setter的任何其餘設置工做也能夠在此時完成。

若是您不須要計算屬性,但仍然須要提供在設置新值以前和以後運行的代碼,請使用willSet和didSet。只要值在初始化器以外發生更改,就會運行您提供的代碼。例如,下面的類確保三角形的邊長始終與正方形的邊長相同。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"
複製代碼

當使用可選值時,您能夠在方法、屬性和下標等操做以前寫?。若是?前面的值是nil,?後面的每一個東西和整個表達式的值都爲nil。不然,可選值將被解包裝,而?做用於未包裝的值。在這兩種狀況下,整個表達式的值都是可選值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
複製代碼

3.5 枚舉和結構體

使用enum建立枚舉。與類和全部其餘命名類型同樣,枚舉能夠有與之關聯的方法。

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
複製代碼

實驗 寫一個函數,比較兩個秩值的原始值。

默認狀況下,Swift從0開始分配原始值,每次遞增1,但您能夠經過顯式指定值來改變這種行爲。在上面的示例中,Ace被顯式地賦予一個原始值1,其他的原始值是按順序分配的。還可使用字符串或浮點數做爲枚舉的原始類型。使用rawValue屬性訪問枚舉用例的原始值。

使用init?(rawValue:)初始化器從原始值生成枚舉的實例。它返回與原始值匹配的枚舉用例,若是沒有匹配的Rank,則返回nil。

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}
複製代碼

枚舉的case值是實際值,而不只僅是編寫原始值的另外一種方式。事實上,在沒有有意義的原始值的狀況下,您沒必要提供。

enum Suit {
    case spades, hearts, diamonds, clubs

    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
複製代碼

實驗 添加一個color()方法,使其對黑桃和梅花返回「黑色」,對紅心和方塊返回「紅色」。

代碼以下:

enum CardColor {
    case Heitao, Meihua, Fangkuai, Hongxin
    
    func color() -> String {
        switch self {
        case .Heitao:
            return "black"
        case .Meihua:
            return "black"
        case .Fangkuai:
            return "red"
        case .Hongxin:
            return "red"
        }
    }
}
複製代碼

請注意上面引用枚舉的hearts case的兩種方式:當爲hearts常量賦值時,因爲該常量沒有明確指定類型,因此引用枚舉case的Suit.hearts全名。在switch內部,枚舉case經過縮寫形式.hearts來引用,由於self的值已經知道是一個suit。只要值的類型已知,就可使用縮寫形式。

若是枚舉具備原始值,則這些值將做爲聲明的一部分,這意味着特定枚舉case的每一個實例始終具備相同的原始值。枚舉cases的另外一種選擇是擁有與該case相關聯的值——這些值是在建立實例時肯定的,對於枚舉case的每一個實例,它們能夠是不一樣的。能夠將關聯值看做相似於枚舉case實例的存儲屬性。例如,考慮從服務器請求日出和日落時間的狀況。服務器要麼響應請求的信息,要麼響應錯誤的描述。

enum ServerResponse {
    case result(String, String)
    case failure(String)
}

let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure... \(message)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."
複製代碼

實驗 添加第三個case到ServerResponse和switch

請注意,日出和日落時間是如何從ServerResponse值中提取的,做爲與switch cases匹配值的一部分。

使用struct建立結構體。結構體支持許多與類相同的行爲,包括方法和初始化器。結構體和類之間最重要的區別之一是,當結構體在代碼中傳遞時,它們老是複製的(值傳遞),而類是經過引用傳遞的(引用傳遞)。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
複製代碼

實驗 編寫一個函數返回一個數組,該數組包含一副牌,每副牌的等級和花色組合各一張牌。

3.6 協議和擴展

使用protocol定義協議

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}
複製代碼

類、枚舉和結構均可以採用協議。

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += " Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
複製代碼

實驗 向ExampleProtocol添加另外一個需求。您須要對SimpleClass和SimpleStructure作哪些更改,以使它們仍然符合協議?

注意,在SimpleStructure的聲明中使用了mutating關鍵字來標記修改結構體的方法。SimpleClass的聲明不須要將其任何方法標記爲mutating,由於類上的方法老是能夠修改類。

使用extension向現有類型添加功能,如新方法和計算屬性。可使用擴展將協議一致性添加到別處聲明的類型,甚至添加到從庫或框架導入的類型。

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)
// Prints "The number 7"
複製代碼

實驗 爲Double類型編寫擴展,以添加absoluteValue屬性。

您能夠像使用任何其餘命名類型同樣使用協議名稱—例如,建立具備不一樣類型但都符合單一協議的對象集合。當處理其類型爲協議類型的值時,協議定義以外的方法不可用。

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty)  // Uncomment to see the error
複製代碼

即便可變protocolValue的運行時類型是SimpleClass,編譯器也會將其視爲給定類型的ExampleProtocol。這意味着您不能意外地訪問類實現的方法或屬性,而不只僅是其協議一致性。

3.7 錯誤處理

可使用任何採用錯誤協議的類型表示錯誤。

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}
複製代碼

使用throw來拋出錯誤,使用throws來標記能夠拋出錯誤的函數。若是在函數中拋出錯誤,函數當即返回,調用該函數的代碼處理錯誤。

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}
複製代碼

有幾種處理錯誤的方法。一種方法是使用do-catch。在do塊中,能夠經過在它前面寫入try來標記可能拋出錯誤的代碼。在catch塊中,除非您給它起了一個不一樣的名字,不然錯誤將自動被命名爲錯誤。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// Prints "Job sent"
複製代碼

實驗 將打印機名稱改成「Never have Toner」,使send(job:toPrinter:)函數拋出一個錯誤。

您能夠提供多個catch塊來處理特定的錯誤。在catch以後編寫模式,就像在switch中的case以後編寫模式同樣。

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// Prints "Job sent"
複製代碼

實驗 添加在do塊中拋出錯誤的代碼。爲了讓第一個catch塊處理錯誤,您須要拋出什麼類型的錯誤?第二和第三個blocks怎麼樣?

處理錯誤的另外一種方法是使用**try?**將結果轉換爲可選的。若是函數拋出一個錯誤,特定的錯誤將被丟棄,結果爲nil。不然,結果是可選的,包含函數返回的值。

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
複製代碼

使用defer編寫一個代碼塊,該代碼塊在函數中全部其餘代碼以後執行,就在函數返回以前執行。不管函數是否拋出錯誤,代碼都將執行。您可使用defer相鄰地編寫設置和清理代碼,即便它們須要在不一樣的時間執行。

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
print(fridgeIsOpen)
// Prints "false"
複製代碼

3.8 泛型

在尖括號內編寫名稱,使其成爲泛型函數或類型。

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
複製代碼

您能夠建立泛型形式的函數和方法,以及類、枚舉和結構。

// Reimplement the Swift standard library's optional type enum OptionalValue<Wrapped> { case none case some(Wrapped) } var possibleInteger: OptionalValue<Int> = .none possibleInteger = .some(100) 複製代碼

在類型名後面使用where關鍵字能夠定義一個限制列表,例如,限制類型實現某一協議,或者要求兩個類型相同,或者要求類繼承某個父類.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])
複製代碼

實驗 修改anyCommonElements(_ :_ :)函數,使其返回任意兩個序列共有的元素數組。

在上面的例子中,你能夠忽略 where ,在冒號後面只寫協議名或者類名。寫法 <T: Equatable>與寫法做用是相同的.

下一章節:基礎

參考文檔:Swift - WELCOME TO SWIFT

相關文章
相關標籤/搜索