【譯】Swift4.2-WELCOME TO SWIFT

關(guān)于Swift

Swift是一個(gè)非常棒的開發(fā)工具凫佛,無論是用于手機(jī)愧薛、臺(tái)式機(jī)、服務(wù)器或者其他任何運(yùn)行代碼的平臺(tái)碘箍。它將現(xiàn)代編程語言的精髓和Apple工程文化與開源社區(qū)的大力貢獻(xiàn)結(jié)合起來丰榴,成為一種安全、迅速盗蟆、交互式的編程語言。編譯器優(yōu)化性能仆邓,語言優(yōu)化開發(fā)方式,兩者互不干擾搞疗。

對(duì)于剛開始學(xué)習(xí)編程的人來說,Swift是非常友好的幢炸。它是一個(gè)既滿足工業(yè)標(biāo)準(zhǔn)又像腳本語言一樣充滿表現(xiàn)力和趣味的編程語言。在playground中書寫Swift代碼岩调,無需構(gòu)建和運(yùn)行應(yīng)用程序就能看到代碼的運(yùn)行結(jié)果。

Swift按照現(xiàn)代編程模式定義了一些常見的編程錯(cuò)誤:

  • 變量應(yīng)該先初始化再使用
  • 檢查數(shù)組索引是否越界
  • 檢查整數(shù)是否溢出
  • Optional要處理值為nil的情況
  • 自動(dòng)管理內(nèi)存
  • 意外故障可以通過拋出錯(cuò)誤進(jìn)行處理

Swift充分利用硬件設(shè)施來編譯和優(yōu)化代碼葱淳,基于代碼清晰明了且運(yùn)行性能最佳的原則來設(shè)計(jì)語法和標(biāo)準(zhǔn)庫(kù)艳狐,它將安全和速度相結(jié)合,使得Swift成為從"Hello world"到整個(gè)操作系統(tǒng)的最佳選擇诲侮。

Swift將強(qiáng)大的類型推斷和模式匹配與現(xiàn)代輕量級(jí)語法結(jié)合起來镀虐,以簡(jiǎn)潔明了的方式表達(dá)復(fù)雜的思想。因此沟绪,代碼不僅更容易編寫刮便,也更容易閱讀和維護(hù)。

多年來Swift隨著新特性和功能的出現(xiàn)不斷在發(fā)展绽慈。我們對(duì)Swift寄予了很大的期望窖杀,迫不及待的想看看你用它創(chuàng)造出來的奇跡桌硫。

版本兼容性

本書描述了Swift4.2,也就是Xcode 10.0Swift的默認(rèn)版本。你可以使用Xcode 10.0構(gòu)建Swift 4或者Swift 3的項(xiàng)目。
注意
當(dāng)使用Swift 4.2的編譯器編譯Swift 3的代碼時(shí),它會(huì)將其語言版本標(biāo)識(shí)為3.4。因此旭贬,你可以使用#if swift(>=3.4)這樣的條件編譯塊來編寫版本兼容的代碼。

當(dāng)使用Xcode 9.2來構(gòu)建Swift 3的代碼時(shí),Swift 4中的大多數(shù)功能都是可用的雌澄。需要注意的是睬涧,下面的功能僅能在Swift 4中使用:

  • Substring的相關(guān)操作將返回Substring類型的實(shí)例宅粥,而不是String
  • 隱式@objc的自動(dòng)推斷僅會(huì)添加在很少的地方。
  • 同一文件中類擴(kuò)展可以訪問該類的私有成員朵诫。

Swift 4編寫的項(xiàng)目可以依賴Swift 3編寫的項(xiàng)目邑滨,反之亦然幢竹。這意味著如果你有一個(gè)被劃分為多個(gè)框架的大型項(xiàng)目邑飒,你可以逐一將框架從Swift 3遷移到Swift 4贼穆。

Swift教程

一般來說新語言中第一個(gè)項(xiàng)目應(yīng)該是在屏幕上打印"Hello world"戴甩。在Swift中,一行代碼就可以實(shí)現(xiàn):

print("Hello world!")

如果你寫過CObjective-C代碼况凉,這種語法對(duì)你來說應(yīng)該很熟悉嫂丙,在Swift中武福,這行代碼就是一個(gè)完整的程序宗雇。你不需要為輸入/輸出或字符串處理等功能導(dǎo)入單獨(dú)的庫(kù)矾兜。在全局作用域編寫的代碼被用做程序的入口,因此不需要寫main()函數(shù)。也不需要在每個(gè)語句的末尾寫分號(hào)嫌褪。

本教程通過演示如何完成各種編程任務(wù)谭胚,來讓你對(duì)Swift有足夠的了解并可以開始書寫Swift代碼纷宇。如果有什么地方不明白不要擔(dān)心硼莽,這本書的其余部分將會(huì)詳細(xì)解釋本教程中介紹的所有內(nèi)容套像。
注意
本章內(nèi)容最好在Xcodeplayground中練習(xí),因?yàn)?code>playground可以在編輯代碼的同時(shí)看到運(yùn)行結(jié)果。

簡(jiǎn)單值

使用let創(chuàng)建常量坷备,使用var創(chuàng)建變量。編譯時(shí)不需要知道常量的值情臭,但是必須要為常量賦值一次击你。也就是說你可以使用常量來命名一個(gè)值玉组,一次賦值就可在多個(gè)地方使用。

var myVariable = 42
myVariable = 50
let myConstant = 42

常量或者變量賦值時(shí)類型必須要與聲明類型相同丁侄。不用總在聲明時(shí)指定類型惯雳,可以在創(chuàng)建常量或變量時(shí)設(shè)置初始值讓編譯器自動(dòng)推斷其類型。在上面的例子中鸿摇,myVariable的初始值是整數(shù)石景,所以編譯器會(huì)認(rèn)為myVariable是整數(shù)類型。

如果初始值沒有提供足夠的信息(或者沒有初始值)拙吉,那么可以在變量后面用冒號(hào)分割來指定類型潮孽。

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

練習(xí)
創(chuàng)建一個(gè)顯式浮點(diǎn)類型值為4的常量

值永遠(yuǎn)不會(huì)隱氏的轉(zhuǎn)換為另一種類型。如果需要將值轉(zhuǎn)換為另一種類型筷黔,需要顯式的創(chuàng)建所需類型的實(shí)例往史。

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

練習(xí)
試著將最后一行String()的強(qiáng)制轉(zhuǎn)換刪掉,看看有什么錯(cuò)誤?

在字符串中包含常量或變量值還有另一種更簡(jiǎn)便的寫法:在括號(hào)中寫入值佛舱,并在括號(hào)前寫一個(gè)反斜杠(\)椎例。例如:

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

練習(xí)
使用\()把浮點(diǎn)運(yùn)算轉(zhuǎn)為字符串,并加上某人的姓名和他打個(gè)招呼请祖。

對(duì)于占用多行代碼的字符串订歪,使用三個(gè)雙引號(hào)("""),只要匹配到結(jié)束引號(hào)肆捕,每個(gè)引用行前面的縮進(jìn)將會(huì)被刪除刷晋。例如:

let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
Except for this line.
Double quotes (") can appear without being escaped.

I still have \(apples + oranges) pieces of fruit.
    """

使用方括號(hào)([])來創(chuàng)建數(shù)組和字典,通過在方括號(hào)中寫入索引或鍵來訪問元素慎陵⊙凼可以在最后一個(gè)元素后面加逗號(hào)。

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

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

數(shù)組在添加元素時(shí)會(huì)自動(dòng)擴(kuò)展其占用大小席纽。

shoppingList.append("blue paint")
print(shoppingList)

如果想要?jiǎng)?chuàng)建空數(shù)組或字典蒙幻,請(qǐng)使用構(gòu)造函數(shù)。

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

如果可以推斷類型信息胆筒,那么可以將空數(shù)組寫成[],空字典寫成[:]诈豌。例如仆救,給變量設(shè)置新值或?qū)?shù)傳給函數(shù)時(shí)。

shoppingList = []
occupations = [:]

控制流

使用ifswitch創(chuàng)建條件語句矫渔,使用for-in彤蔽、whilerepeat-while來創(chuàng)建循環(huán)。包裹條件或循環(huán)變量的括號(hào)可以省略庙洼,但是實(shí)現(xiàn)部分的括號(hào)是必須的顿痪。

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語句中镊辕,條件必須是布爾表達(dá)式,這就意味著if score {...}這樣寫是錯(cuò)誤的蚁袭,這并不會(huì)與0做隱式比較。

你可以用iflet來處理可能為nil的值,這些值用Optional來表示花颗。一個(gè)Optional要么有值弊添,要么用nil來表示值缺失。在類型后面加一個(gè)問號(hào)(?)來表示此值是Optional的删性。

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

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

練習(xí)
optionalName改為nil亏娜,greeting將會(huì)變成什么? 如果optionalNamenil,加一個(gè)else來設(shè)置一個(gè)不同的greeting蹬挺。

如果Optional的值為nil维贺,則條件為false,并跳過大括號(hào)中的代碼巴帮。否則溯泣,Optional的值將會(huì)被解析然后賦值給let之后的常量,這樣就可以在代碼塊中使用解析后的值晰韵。

另一種處理Optional值的方法是使用??操作符发乔。如果Optional沒有賦值,則使用默認(rèn)值雪猪。

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.")
}
// Prints "Is it a spicy red pepper?"

練習(xí)
試著刪除default分支只恨,看看會(huì)得到什么錯(cuò)誤?

請(qǐng)注意上例中如何使用let將匹配的值賦值給常量x译仗。

switch匹配到某一個(gè)case分支之后,將會(huì)執(zhí)行相應(yīng)代碼官觅,代碼執(zhí)行完畢之后纵菌,程序?qū)?code>switch語句中退出。不會(huì)執(zhí)行到下一個(gè)分支休涤,因此無需在每個(gè)條件分支的代碼末尾顯氏的退出(就是不用寫break了)咱圆。

通過為每一個(gè)鍵值對(duì)提供一對(duì)名稱,可以使用for-in遍歷字典中的每一項(xiàng)功氨。字典是一個(gè)無序集合序苏,因此它們的鍵和值是無序遍歷的。

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"

練習(xí)
添加一個(gè)額外變量捷凄,找出哪種類型的數(shù)字最大忱详,以及最大的數(shù)字是多少。

使用while來執(zhí)行循環(huán)代碼跺涤,直到條件發(fā)生改變匈睁。循環(huán)的條件可以寫在末尾监透,以確保循環(huán)至少運(yùn)行一次。

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"

可以使用..<來循環(huán)某個(gè)范圍內(nèi)的每一個(gè)索引航唆。

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

使用..<表示不包含范圍右邊最大值的取值胀蛮。使用...表示包含范圍兩邊的取值。

函數(shù)和閉包

使用func來聲明函數(shù)佛点。通過在函數(shù)名后面加上圓括號(hào)以及括號(hào)的參數(shù)列表來調(diào)用函數(shù)醇滥。使用->將參數(shù)的名稱和類型與函數(shù)的返回值區(qū)分開來。

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

練習(xí)
刪除參數(shù)day超营。添加一個(gè)參數(shù)鸳玩,使問候語中包含今天的午餐。

默認(rèn)情況下演闭,函數(shù)使用參數(shù)名作為參數(shù)的標(biāo)簽不跟,在參數(shù)名之前編寫自定義的參數(shù)標(biāo)簽,或者寫一個(gè)_來表示不使用參數(shù)標(biāo)簽米碰。

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

使用元組生成復(fù)合值窝革。例如,函數(shù)的返回值有多個(gè)吕座。元組中的元素可以通過名稱或下標(biāo)來表示虐译。

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"

函數(shù)可以嵌套使用,嵌套在內(nèi)的函數(shù)可以訪問在該函數(shù)外部中聲明的變量吴趴∑岱蹋可以使用嵌套函數(shù)來重構(gòu)一個(gè)太長(zhǎng)或太復(fù)雜的函數(shù)。

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

函數(shù)是一種類型锣枝,這也就是說函數(shù)的返回值可以是另一個(gè)函數(shù)厢拭。

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

同樣,函數(shù)的參數(shù)也可以是另一個(gè)函數(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ù)實(shí)際上是一種特殊的閉包:可以稍后調(diào)用的代碼塊供鸠。即使閉包的定義和執(zhí)行在兩個(gè)不同的作用域中,閉包中的代碼仍可以訪問定義閉包時(shí)作用域內(nèi)的變量和函數(shù)陨闹。也可以使用大括號(hào)({})來創(chuàng)建一個(gè)匿名閉包楞捂。使用in把參數(shù)和返回值與函數(shù)體分隔開。

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

練習(xí)
試著寫一個(gè)對(duì)于所有奇數(shù)返回值均為0的閉包趋厉。

閉包有幾種簡(jiǎn)單的定義方式寨闹。當(dāng)已知閉包類型時(shí)(比如委托回調(diào)),就可以省略其參數(shù)類型或返回值類型觅廓,或兩者都省略。單個(gè)語句的閉包默認(rèn)返回其唯一表達(dá)式的值涵但。

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

在短閉包中可以使用下標(biāo)來引用參數(shù)杈绸。作為函數(shù)最后一個(gè)參數(shù)的閉包可以直接在括號(hào)后面書寫帖蔓,當(dāng)閉包是函數(shù)的唯一一個(gè)參數(shù)時(shí),括號(hào)可以省略瞳脓。

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

對(duì)象和類

使用class和類名來創(chuàng)建類塑娇。類中屬性的聲明與常量、變量的聲明相同劫侧,唯一的區(qū)別就是將它寫在類中而已埋酬。同樣,方法和函數(shù)聲明也一樣烧栋。

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

練習(xí)
使用let聲明一個(gè)常量写妥,再寫一個(gè)帶有一個(gè)參數(shù)的方法。

通過在類名后面加上括號(hào)來創(chuàng)建類的實(shí)例审姓。使用點(diǎn)語法訪問實(shí)例的屬性和方法珍特。

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

上面創(chuàng)建Shape類的時(shí)候少寫了一個(gè)重要的函數(shù):在創(chuàng)建實(shí)例時(shí)需要調(diào)用類的構(gòu)造函數(shù)。使用init來創(chuàng)建實(shí)例魔吐。

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

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

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

使用selfname屬性和構(gòu)造函數(shù)中的name參數(shù)區(qū)分開來扎筒。當(dāng)創(chuàng)建類實(shí)例時(shí),構(gòu)造函數(shù)參數(shù)的傳遞方式和函數(shù)調(diào)用參數(shù)的傳遞方式一樣酬姆。每個(gè)屬性都需要賦值嗜桌,無論是在聲明中直接賦值(比如numberOfSides),還是在構(gòu)造函數(shù)中賦值(比如name)辞色。

如果需要在對(duì)象銷毀時(shí)進(jìn)行清理工作骨宠,請(qǐng)使用deinit創(chuàng)建一個(gè)析構(gòu)器。

可以通過在子類類名和父類類名之間用冒號(hào)分隔的方法來創(chuàng)建子類淫僻。創(chuàng)建類的時(shí)候不需要有一個(gè)標(biāo)準(zhǔn)的根類诱篷,所以可以根據(jù)需要包含或者省略父類。

子類重寫父類的方法時(shí)要寫override關(guān)鍵字雳灵。如果沒寫override就重寫父類方法的話棕所,編譯器會(huì)報(bào)錯(cuò)。編譯器也會(huì)檢測(cè)override標(biāo)記的方法是否確實(shí)在父類中悯辙。

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

練習(xí)
創(chuàng)建一個(gè)繼承于NamedShape類的Circle類琳省。該類的構(gòu)造函數(shù)有兩個(gè)參數(shù):radiuname,并且實(shí)現(xiàn)area()simpleDescription()方法躲撰。

除了存儲(chǔ)簡(jiǎn)單的執(zhí)行值之外针贬,屬性還有gettersetter方法。

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"

在屬性perimetersetter方法中拢蛋,新值默認(rèn)名稱為newValue桦他,也可以在set之后的括號(hào)里指定新值的名稱。

請(qǐng)注意類EquilateralTriangle的初始化有三步:
1. 設(shè)置子類聲明的屬性值谆棱。
2. 調(diào)用父類的構(gòu)造函數(shù)快压。
3. 重設(shè)父類中定義的屬性值圆仔。此時(shí)也可以調(diào)用gettersetter或其他方法蔫劣。

如果不需要對(duì)屬性值進(jìn)行計(jì)算坪郭,但是仍想監(jiān)測(cè)該屬性被設(shè)置新值之前或之后的話,可以使用willSetdidSet脉幢。除了構(gòu)造函數(shù)之外歪沃,任何改變?cè)撝档牡胤蕉紩?huì)被調(diào)用。例如嫌松,實(shí)現(xiàn)一個(gè)類使得三角形和正方形邊長(zhǎng)始終相等沪曙。

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"

在處理Optional值的時(shí)候,可以在方法豆瘫、屬性或下標(biāo)之前寫一個(gè)?珊蟀,如果?之前的值為nil?之后的值都將會(huì)被忽略外驱,且整個(gè)表達(dá)式的返回值為nil育灸。反之,將會(huì)解析Optional的值昵宇,?之后的表達(dá)式將會(huì)作用在被解析的值上磅崭。這兩種情況下,整個(gè)表達(dá)式的值都是一個(gè)Optional值瓦哎。

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

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

使用enum來創(chuàng)建枚舉器砸喻。像類和其他命名類型一樣,枚舉可以有和它們相關(guān)聯(lián)的方法蒋譬。

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

練習(xí)
寫一個(gè)通過比較原始值來排序的函數(shù)割岛。

默認(rèn)情況下,Swift的原始值從0開始犯助,按順序依次遞增1癣漆,不過也可以顯式的指定原始值。在上例中剂买,ace的原始值就被顯式的賦值為1惠爽。其余的原始值按順序依次遞增。你也可以使用字符串或浮點(diǎn)型數(shù)據(jù)作為枚舉的原始值瞬哼。使用rawValue屬性訪問枚舉的原始值婚肆。

使用init?(rawValue:)函數(shù)傳入原始值來初始化枚舉器實(shí)例。如果該原始值存在則返回枚舉器實(shí)例坐慰,如果不存在則返回nil较性。

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

枚舉器的枚舉值不僅僅是原始值的另一種寫法,它們是有實(shí)際意義的。如果原始值沒有實(shí)際意義的話赞咙,就不用寫原始值永毅。

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

練習(xí)
添加一個(gè)color()方法,使spadesclubs返回black人弓,heartsdiamonds返回red

上面用了兩種方式引用了枚舉值hearts:給常量hearts賦值時(shí)着逐,由于該常量沒有顯示的指定類型崔赌,所以需要用全名Suit.hearts來引用。在switch中耸别,因?yàn)橐阎?code>self是Suit類型健芭,所以可以用.hearts這種縮寫形式來引用。也就是說秀姐,只要值的類型已知慈迈,那么就可以使用縮寫形式。

如果枚舉值有原始值省有,那么這些原始值在聲明的時(shí)候就已經(jīng)確定了痒留。這意味著對(duì)于同一種類型的枚舉器,其實(shí)例的原始值總是相同的蠢沿。當(dāng)然也可以為枚舉值設(shè)置關(guān)聯(lián)值伸头,這些值是在創(chuàng)建實(shí)例時(shí)傳入的,此時(shí)舷蟀,同一種類型的枚舉器恤磷,其實(shí)例的原始值就是不同的。你也可以認(rèn)為關(guān)聯(lián)值其實(shí)就是枚舉值的屬性野宜。例如扫步,從服務(wù)端請(qǐng)求日出和日落時(shí)間時(shí),服務(wù)端將會(huì)響應(yīng)該請(qǐng)求或者返回錯(cuò)誤信息匈子。

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

練習(xí)
ServerResponse再添加一個(gè)枚舉值河胎。

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

使用struct創(chuàng)建結(jié)構(gòu)體旬牲。結(jié)構(gòu)體和類很相似仿粹。都有方法和構(gòu)造函數(shù)。結(jié)構(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()

練習(xí)
編寫一個(gè)函數(shù)擂橘,該函數(shù)的返回值為一副完整的紙牌晌区,并且每張牌的ranksuit一一對(duì)應(yīng)。

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

使用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

練習(xí)
ExampleProtocol中添加另一個(gè)方法,如何修改SimpleClassSimpleStructure才能使它們?nèi)阅茏袷卦搮f(xié)議?

注意SimpleStructure的聲明中使用關(guān)鍵字mutating來表示可以在該方法中修改結(jié)構(gòu)體(注:枚舉變量也需要加mutating關(guān)鍵字)哭懈。而在SimpleClass中不需要寫mutating灾馒,因?yàn)樵陬愔锌梢噪S意的修改自己的成員變量和方法。

使用extension向現(xiàn)有類型中添加功能遣总,比如新方法和屬性睬罗。無論是從庫(kù)還是框架中導(dǎo)入或是其他任何地方定義的類型,都可以使用擴(kuò)展給該類型添加協(xié)議旭斥。

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

練習(xí)
Double類型擴(kuò)展一個(gè)absoluteValue屬性容达。

也可以像使用其他類型一樣使用協(xié)議巩搏,例如哮幢,創(chuàng)建一個(gè)遵守該協(xié)議但屬于不同類型的對(duì)象集合铐姚。當(dāng)使用協(xié)議類型時(shí)诺舔,除了協(xié)議定義的方法其他方法都不可用琐凭。

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

即使運(yùn)行時(shí)protocolValueSimpleClass類型的朴下,但編譯器還是會(huì)認(rèn)為它是ExampleProtocol類型的棵譬。這也就是說除了協(xié)議中定義的方法侥啤,protocolValue無法訪問SimpleClass類的方法或?qū)傩浴?/p>

錯(cuò)誤處理

任何遵守Error協(xié)議的類型都可以用來表示錯(cuò)誤凳宙。

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

使用throw來拋出一個(gè)錯(cuò)誤也祠,使用throws來表示一個(gè)可能會(huì)拋出錯(cuò)誤的函數(shù)。如果函數(shù)拋出了錯(cuò)誤近速,其將立即返回并調(diào)用錯(cuò)誤處理诈嘿。

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

處理錯(cuò)誤有很多種方式。其中一種是使用do-catch削葱。在do代碼塊中奖亚,使用關(guān)鍵字try來標(biāo)記可能會(huì)拋出錯(cuò)誤的代碼。除非自行指定錯(cuò)誤名稱析砸,否則錯(cuò)誤名稱默認(rèn)為error昔字。

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

練習(xí)
printerName改為Never Has Tonersend(job:toPrinter:)就會(huì)拋出錯(cuò)誤首繁。

也可以使用多個(gè)catch塊來處理不同的錯(cuò)誤類型作郭。就像在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"

練習(xí)
do代碼塊中添加拋出錯(cuò)誤的代碼弦疮,如果想要在第一個(gè)catch塊中捕獲夹攒,需要拋出什么類型的錯(cuò)誤?如果要在第二個(gè)或第三個(gè)catch塊中捕獲呢?

另一種處理錯(cuò)誤的方法是使用try?將結(jié)果轉(zhuǎn)為Optional類型。如果函數(shù)拋出了錯(cuò)誤胁塞,該錯(cuò)誤會(huì)被拋棄并且返回值為nil咏尝。否則压语,返回值為Optional類型。

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

可以使用defer來保存需要在函數(shù)其他代碼執(zhí)行之后编检、return執(zhí)行之前執(zhí)行的代碼胎食。無論函數(shù)是否拋出錯(cuò)誤,defer中代碼的都會(huì)執(zhí)行允懂。也可以使用defer將觸發(fā)時(shí)機(jī)不同的設(shè)置代碼和還原代碼寫在一起厕怜。

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"

泛型

在尖括號(hào)內(nèi)寫一個(gè)名字來創(chuàng)建一個(gè)泛型函數(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)

泛型也可以用于函數(shù)蕾总、方法酣倾、類、枚舉和結(jié)構(gòu)體谤专。

enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

在類型名后面使用where來指定對(duì)類型的要求,例如午绳,限定類型必須遵守某個(gè)協(xié)議置侍,限定兩個(gè)類型必須相同,或者限定該類必須有一個(gè)特定的父類拦焚。

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

練習(xí)
修改anyCommonElements(_:_:)函數(shù)蜡坊,使其返回由任意兩個(gè)序列中相同元素組成的數(shù)組。

<T: Equatable><T> ... where T: Equatable這兩種寫法是等價(jià)的赎败。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秕衙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子僵刮,更是在濱河造成了極大的恐慌据忘,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搞糕,死亡現(xiàn)場(chǎng)離奇詭異勇吊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)窍仰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門汉规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人驹吮,你說我怎么就攤上這事针史。” “怎么了碟狞?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵啄枕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我族沃,道長(zhǎng)射亏,這世上最難降的妖魔是什么近忙? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮智润,結(jié)果婚禮上及舍,老公的妹妹穿的比我還像新娘。我一直安慰自己窟绷,他們只是感情好锯玛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兼蜈,像睡著了一般攘残。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上为狸,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天歼郭,我揣著相機(jī)與錄音,去河邊找鬼辐棒。 笑死病曾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漾根。 我是一名探鬼主播泰涂,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼辐怕!你這毒婦竟也來了逼蒙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤寄疏,失蹤者是張志新(化名)和其女友劉穎是牢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陕截,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妖泄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艘策。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹈胡。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖朋蔫,靈堂內(nèi)的尸體忽然破棺而出罚渐,到底是詐尸還是另有隱情,我是刑警寧澤驯妄,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布荷并,位于F島的核電站,受9級(jí)特大地震影響青扔,放射性物質(zhì)發(fā)生泄漏源织。R本人自食惡果不足惜翩伪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谈息。 院中可真熱鬧缘屹,春花似錦、人聲如沸侠仇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逻炊。三九已至互亮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間余素,已是汗流浹背豹休。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桨吊,地道東北人威根。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屏积,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子磅甩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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