鍍金池/ 教程/ iOS/ Swift 初見(A Swift Tour)
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運算符(Basic Operators)
基礎部分(The Basics)
閉包(Closures)
擴展
泛型參數(shù)(Generic Parameters and Arguments)
訪問控制和 protected
語句(Statements)
模式(Patterns)
WWDC 里面的那個“大炮打氣球”
關(guān)于語言參考(About the Language Reference)
語法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(A Swift Tour)
泛型
枚舉(Enumerations)
高級運算符
繼承
析構(gòu)過程
關(guān)于 Swift(About Swift)
訪問控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問題
錯誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動引用計數(shù)
Swift 里的值類型與引用類型
表達式(Expressions)
Swift 文檔修訂歷史
造個類型不是夢-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標
方法(Methods)
可選鏈式調(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

Swift 初見(A Swift Tour)


1.0 翻譯:numbbbbb 校對:shinyzhu, stanzhai

2.0 翻譯+校對:xtymichael

2.2 翻譯:175,2016-04-09 校對:SketchK,2016-05-11

3.0 翻譯+校對:shanks,2016-10-06

3.0.1 review: 2016-11-09

3.1 校對: SketchK 2017-04-08

4.0 翻譯+校對:muhlenxi 2017-09-26

4.1 翻譯:mylittleswift

本頁內(nèi)容包括:

通常來說,編程語言教程中的第一個程序應該在屏幕上打印 “Hello, world”。在 Swift 中,可以用一行代碼實現(xiàn):

print("Hello, world!")

如果你寫過 C 或者 Objective-C 代碼,那你應該很熟悉這種形式——在 Swift 中,這行代碼就是一個完整的程序。你不需要為了輸入輸出或者字符串處理導入一個單獨的庫。全局作用域中的代碼會被自動當做程序的入口點,所以你也不需要 main() 函數(shù)。你同樣不需要在每個語句結(jié)尾寫上分號。

這個教程會通過一系列編程例子來讓你對 Swift 有初步了解,如果你有什么不理解的地方也不用擔心——任何本章介紹的內(nèi)容都會在后面的章節(jié)中詳細講解到。

注意

最好的體驗是把這一章作為 Playground 文件在 Xcode 中打開。 Playgrounds 允許你可以編輯代碼并立刻看到輸出結(jié)果。

Download Playground

簡單值

使用 let 來聲明常量,使用 var 來聲明變量。一個常量的值,在編譯的時候,并不需要有明確的值,但是你只能為它賦值一次。這說明你可以用一個常量來命名一個值,一次賦值就即可在多個地方使用。

var myVariable = 42
myVariable = 50
let myConstant = 42

常量或者變量的類型必須和你賦給它們的值一樣。然而,你不用明確地聲明類型。當你通過一個值來聲明變量和常量時,編譯器會自動推斷其類型。在上面的例子中,編譯器推斷出 myVariable 是一個整數(shù)類型,因為它的初始值是整數(shù)。

如果初始值沒有提供足夠的信息(或者沒有初始值),那你需要在變量后面聲明類型,用冒號分割。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

練習

創(chuàng)建一個常量,顯式指定類型為 Float 并指定初始值為 4。

值永遠不會被隱式轉(zhuǎn)換為其他類型。如果你需要把一個值轉(zhuǎn)換成其他類型,請顯式轉(zhuǎn)換。

let label = "The width is"
let width = 94
let widthLabel = label + String(width)

練習

刪除最后一行中的 String,錯誤提示是什么?

有一種更簡單的把值轉(zhuǎn)換成字符串的方法:把值寫到括號中,并且在括號之前寫一個反斜杠(\)。例如:

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

練習

使用 \() 來把一個浮點計算轉(zhuǎn)換成字符串,并加上某人的名字,和他打個招呼。

使用一對三個單引號(""")來包含多行字符串內(nèi)容,字符串中的內(nèi)容(包括引號、空格、換行符等)都會保留下來。舉個例子:

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

使用方括號 [] 來創(chuàng)建數(shù)組和字典,并使用下標或者鍵(key)來訪問元素。最后一個元素后面允許有個逗號。

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

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

使用初始化語法來創(chuàng)建一個空數(shù)組或者空字典。

let emptyArray = [String]()
let emptyDictionary = [String: Float]()

如果類型信息可以被推斷出來,你可以用 [][:] 來創(chuàng)建空數(shù)組和空字典——就像你聲明變量或者給函數(shù)傳參數(shù)的時候一樣。

shoppingList = []
occupations = [:]

控制流

使用 ifswitch 來進行條件操作,使用 for-in、whilerepeat-while 來進行循環(huán)。包裹條件和循環(huán)變量的括號可以省略,但是語句體的大括號是必須的。

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

if 語句中,條件必須是一個布爾表達式——這意味著像 if score { ... } 這樣的代碼將報錯,而不會隱形地與 0 做對比。

你可以一起使用 iflet 一起來處理值缺失的情況。這些值可由可選值來代表。一個可選的值是一個具體的值或者是 nil 以表示值缺失。在類型后面加一個問號(?)來標記這個變量的值是可選的。

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

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

練習

optionalName 改成 nil,greeting 會是什么?添加一個 else 語句,當 optionalNamenil 時給 greeting 賦一個不同的值。

如果變量的可選值是 nil,條件會判斷為 false,大括號中的代碼會被跳過。如果不是 nil,會將值解包并賦給 let 后面的常量,這樣代碼塊中就可以使用這個值了。 另一種處理可選值的方法是通過使用 ?? 操作符來提供一個默認值。如果可選值缺失的話,可以使用默認值來代替。

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

switch 支持任意類型的數(shù)據(jù)以及各種比較操作——不僅僅是整數(shù)以及測試相等。

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

練習

刪除 default 語句,看看會有什么錯誤?

注意 let 在上述例子的等式中是如何使用的,它將匹配等式的值賦給常量 x。

運行 switch 中匹配到的 case 語句之后,程序會退出 switch 語句,并不會繼續(xù)向下運行,所以不需要在每個子句結(jié)尾寫 break。

你可以使用 for-in 來遍歷字典,需要一對兒變量來表示每個鍵值對。字典是一個無序的集合,所以他們的鍵和值以任意順序迭代結(jié)束。

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)

練習

添加另一個變量來記錄最大數(shù)字的種類(kind),同時仍然記錄這個最大數(shù)字的值。

使用 while 來重復運行一段代碼直到條件改變。循環(huán)條件也可以在結(jié)尾,保證能至少循環(huán)一次。

var n = 2
while n < 100 {
    n *= 2
}
print(n)

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

你可以在循環(huán)中使用 ..< 來表示下標范圍。

var total = 0
for i in 0..<4 {
    total += i
}
print(total)

使用 ..< 創(chuàng)建的范圍不包含上界,如果想包含的話需要使用 ...。

函數(shù)和閉包

使用 func 來聲明一個函數(shù),使用名字和參數(shù)來調(diào)用函數(shù)。使用 -> 來指定函數(shù)返回值的類型。

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

練習

刪除 day 參數(shù),添加一個參數(shù)來表示今天吃了什么午飯。

默認情況下,函數(shù)使用它們的參數(shù)名稱作為它們參數(shù)的標簽,在參數(shù)名稱前可以自定義參數(shù)標簽,或者使用 _ 表示不使用參數(shù)標簽。

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

使用元組來生成復合值,比如讓一個函數(shù)返回多個值。該元組的元素可以用名稱或數(shù)字來獲取。

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)
print(statistics.2)

函數(shù)可以嵌套。被嵌套的函數(shù)可以訪問外側(cè)函數(shù)的變量,你可以使用嵌套函數(shù)來重構(gòu)一個太長或者太復雜的函數(shù)。

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

函數(shù)是第一等類型,這意味著函數(shù)可以作為另一個函數(shù)的返回值。

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

函數(shù)也可以當做參數(shù)傳入另一個函數(shù)。

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)

函數(shù)實際上是一種特殊的閉包:它是一段能之后被調(diào)取的代碼。閉包中的代碼能訪問閉包作用域中的變量和函數(shù),即使閉包是在一個不同的作用域被執(zhí)行的 - 你已經(jīng)在嵌套函數(shù)的例子中看過了。你可以使用 {} 來創(chuàng)建一個匿名閉包。使用 in 將參數(shù)和返回值類型的聲明與閉包函數(shù)體進行分離。

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

練習

重寫閉包,對所有奇數(shù)返回 0。

有很多種創(chuàng)建更簡潔的閉包的方法。如果一個閉包的類型已知,比如作為一個代理的回調(diào),你可以忽略參數(shù),返回值,甚至兩個都忽略。單個語句閉包會把它語句的值當做結(jié)果返回。

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

你可以通過參數(shù)位置而不是參數(shù)名字來引用參數(shù)——這個方法在非常短的閉包中非常有用。當一個閉包作為最后一個參數(shù)傳給一個函數(shù)的時候,它可以直接跟在括號后面。當一個閉包是傳給函數(shù)的唯一參數(shù),你可以完全忽略括號。

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

對象和類

使用 class 和類名來創(chuàng)建一個類。類中屬性的聲明和常量、變量聲明一樣,唯一的區(qū)別就是它們的上下文是類。同樣,方法和函數(shù)聲明也一樣。

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

練習

使用 let 添加一個常量屬性,再添加一個接收一個參數(shù)的方法。

要創(chuàng)建一個類的實例,在類名后面加上括號。使用點語法來訪問實例的屬性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

這個版本的 Shape 類缺少了一些重要的東西:一個構(gòu)造函數(shù)來初始化類實例。使用 init 來創(chuàng)建一個構(gòu)造器。

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 被用來區(qū)別實例變量 name 和構(gòu)造器的參數(shù) name。當你創(chuàng)建實例的時候,像傳入函數(shù)參數(shù)一樣給類傳入構(gòu)造器的參數(shù)。每個屬性都需要賦值——無論是通過聲明(就像 numberOfSides)還是通過構(gòu)造器(就像 name)。

如果你需要在對象釋放之前進行一些清理工作,使用 deinit 創(chuàng)建一個析構(gòu)函數(shù)。

子類的定義方法是在它們的類名后面加上父類的名字,用冒號分割。創(chuàng)建類的時候并不需要一個標準的根類,所以你可以根據(jù)需要添加或者忽略父類。

子類如果要重寫父類的方法的話,需要用 override 標記——如果沒有添加 override 就重寫父類方法的話編譯器會報錯。編譯器同樣會檢測 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()

練習

創(chuàng)建 NamedShape 的另一個子類 Circle,構(gòu)造器接收兩個參數(shù),一個是半徑一個是名稱,在子類 Circle 中實現(xiàn) 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)
triangle.perimeter = 9.9
print(triangle.sideLength)

perimeter 的 setter 中,新值的名字是 newValue。你可以在 set 之后顯式的設置一個名字。

注意 EquilateralTriangle 類的構(gòu)造器執(zhí)行了三步:

  1. 設置子類聲明的屬性值
  2. 調(diào)用父類的構(gòu)造器
  3. 改變父類定義的屬性值。其他的工作比如調(diào)用方法、getters 和 setters 也可以在這個階段完成。

如果你不需要計算屬性,但是仍然需要在設置一個新值之前或者之后運行代碼,使用 willSetdidSet。寫入的代碼會在屬性值發(fā)生改變時調(diào)用,但不包含構(gòu)造器中發(fā)生值改變的情況。比如,下面的類確保三角形的邊長總是和正方形的邊長相同。

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)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

處理變量的可選值時,你可以在操作(比如方法、屬性和子腳本)之前加 ?。如果 ? 之前的值是 nil,? 后面的東西都會被忽略,并且整個表達式返回 nil。否則,? 之后的東西都會被運行。在這兩種情況下,整個表達式的值也是一個可選值。

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

枚舉和結(jié)構(gòu)體

使用 enum 來創(chuàng)建一個枚舉。就像類和其他所有命名類型一樣,枚舉可以包含方法。

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

練習

寫一個函數(shù),通過比較它們的原始值來比較兩個 Rank 值。

默認情況下,Swift 按照從 0 開始每次加 1 的方式為原始值進行賦值,不過你可以通過顯式賦值進行改變。在上面的例子中,Ace 被顯式賦值為 1,并且剩下的原始值會按照順序賦值。你也可以使用字符串或者浮點數(shù)作為枚舉的原始值。使用 rawValue 屬性來訪問一個枚舉成員的原始值。

使用 init?(rawValue:) 初始化構(gòu)造器來創(chuàng)建一個帶有原始值的枚舉成員。如果存在與原始值相應的枚舉成員就返回該枚舉成員,否則就返回 nil。

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚舉的關(guān)聯(lián)值是實際值,并不是原始值的另一種表達方法。實際上,如果沒有比較有意義的原始值,你就不需要提供原始值。

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

練習

Suit 添加一個 color() 方法,對 spadesclubs 返回 “black” ,對 heartsdiamonds 返回 “red” 。

注意在上面的例子中用了兩種方式引用 hearts 枚舉成員:給 hearts 常量賦值時,枚舉成員 Suit.hearts 需要用全名來引用,因為常量沒有顯式指定類型。在 switch 里,枚舉成員使用縮寫 .hearts 來引用,因為 self 的值已經(jīng)是一個 suit 類型,在已知變量類型的情況下可以使用縮寫。

如果枚舉成員的實例有原始值,那么這些值是在聲明的時候就已經(jīng)決定了,這意味著不同枚舉實例的枚舉成員總會有一個相同的原始值。當然我們也可以為枚舉成員設定關(guān)聯(lián)值,關(guān)聯(lián)值是在創(chuàng)建實例時決定的。這意味著不同的枚舉成員的關(guān)聯(lián)值都可以不同。你可以把關(guān)聯(lián)值想象成枚舉成員的寄存屬性。例如,考慮從服務器獲取日出和日落的時間的情況。服務器會返回正常結(jié)果或者錯誤信息。

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

練習

ServerResponse 和 switch 添加第三種情況。

注意日升和日落時間是如何從 ServerResponse 中提取到并與 switchcase 相匹配的。

使用 struct 來創(chuàng)建一個結(jié)構(gòu)體。結(jié)構(gòu)體和類有很多相同的地方,包括方法和構(gòu)造器。它們之間最大的一個區(qū)別就是結(jié)構(gòu)體是傳值,類是傳引用。

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

練習

Card 添加一個方法,創(chuàng)建一副完整的撲克牌并把每張牌的 rank 和 suit 對應起來。

協(xié)議和擴展

使用 protocol 來聲明一個協(xié)議。

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

類、枚舉和結(jié)構(gòu)體都可以遵循協(xié)議。

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

練習

寫一個實現(xiàn)這個協(xié)議的枚舉。

注意聲明 SimpleStructure 時候 mutating 關(guān)鍵字用來標記一個會修改結(jié)構(gòu)體的方法。SimpleClass 的聲明不需要標記任何方法,因為類中的方法通??梢孕薷念悓傩裕惖男再|(zhì))。

使用 extension 來為現(xiàn)有的類型添加功能,比如新的方法和計算屬性。你可以使用擴展讓某個在別處聲明的類型來遵守某個協(xié)議,這同樣適用于從外部庫或者框架引入的類型。

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

練習

Double 類型寫一個擴展,添加 absoluteValue 屬性。

你可以像使用其他命名類型一樣使用協(xié)議名——例如,創(chuàng)建一個有不同類型但是都實現(xiàn)一個協(xié)議的對象集合。當你處理類型是協(xié)議的值時,協(xié)議外定義的方法不可用。

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty)  // 去掉注釋可以看到錯誤

即使 protocolValue 變量運行時的類型是 simpleClass ,編譯器還是會把它的類型當做 ExampleProtocol。這表示你不能調(diào)用在協(xié)議之外的方法或者屬性。

錯誤處理

使用采用 Error 協(xié)議的類型來表示錯誤。

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

使用 throw 來拋出一個錯誤和使用 throws 來表示一個可以拋出錯誤的函數(shù)。如果在函數(shù)中拋出一個錯誤,這個函數(shù)會立刻返回并且調(diào)用該函數(shù)的代碼會進行錯誤處理。

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 代碼塊中,除非你另外命名,否則錯誤會自動命名為 error 。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}

練習

將 printer name 改為 "Never Has Toner" 使 send(job:toPrinter:) 函數(shù)拋出錯誤。

可以使用多個 catch 塊來處理特定的錯誤。參照 switch 中的 case 風格來寫 catch

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

練習

do 代碼塊中添加拋出錯誤的代碼。你需要拋出哪種錯誤來使第一個 catch 塊進行接收?怎么使第二個和第三個 catch 進行接收呢?

另一種處理錯誤的方式使用 try? 將結(jié)果轉(zhuǎn)換為可選的。如果函數(shù)拋出錯誤,該錯誤會被拋棄并且結(jié)果為 nil。否則,結(jié)果會是一個包含函數(shù)返回值的可選值。

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

使用 defer 代碼塊來表示在函數(shù)返回前,函數(shù)中最后執(zhí)行的代碼。無論函數(shù)是否會拋出錯誤,這段代碼都將執(zhí)行。使用 defer,可以把函數(shù)調(diào)用之初就要執(zhí)行的代碼和函數(shù)調(diào)用結(jié)束時的掃尾代碼寫在一起,雖然這兩者的執(zhí)行時機截然不同。

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)

泛型

在尖括號里寫一個名字來創(chuàng)建一個泛型函數(shù)或者類型。

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)

你也可以創(chuàng)建泛型函數(shù)、方法、類、枚舉和結(jié)構(gòu)體。

// 重新實現(xiàn) Swift 標準庫中的可選類型
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

在類型名后面使用 where 來指定對類型的一系列需求,比如,限定類型實現(xiàn)某一個協(xié)議,限定兩個類型是相同的,或者限定某個類必須有一個特定的父類。

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

練習

修改 anyCommonElements(_:_:) 函數(shù)來創(chuàng)建一個函數(shù),返回一個數(shù)組,內(nèi)容是兩個序列的共有元素。

<T: Equatable><T> ... where T: Equatable> 的寫法是等價的。