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

引言

今天,開始系統(tǒng)學(xué)習(xí)Swift城瞎,以前都是零零散散的看看的let和var的區(qū)別、泛型疾瓮,只知道它是一個面向協(xié)議且類型安全的語言脖镀,性能比OC好多了,也沒有去實(shí)踐過狼电,感覺很空虛(內(nèi)心感受蜒灰,不要想歪)!開發(fā)了那么久才開始加入Swift肩碟,感覺落后了好多(T_T)... 好了强窖,往事不提,從今天開始把它撿起來就好了削祈。覺得對于我而言翅溺,最有效的學(xué)習(xí)方法是:一邊學(xué)習(xí)官方文檔脑漫,一邊來翻譯一遍,印象會更深刻一點(diǎn)(好吧咙崎,我承認(rèn)优幸,這是最沒效率的一種方式T_T,奈何我的記性不是很好褪猛,只能用這種方法了)劈伴。在學(xué)習(xí)過程中,也終于搞清楚了Swift里特有的一些功能的用法握爷,比如元組可選值和解包:?和!严里、泛型新啼、If let和If var等等,說明學(xué)習(xí)一門語言刹碾,還要做從官方文檔開始(個人見解燥撞,不喜勿噴)!接下來迷帜,讓我們一起去探索Swift的奇妙之處吧物舒!Fighting~

如果你已經(jīng)大概了解Swift的基本知識點(diǎn),那么請參閱下一章節(jié):基礎(chǔ)

歡迎來到SWIFT

1 關(guān)于Swift

Swift是一個編寫軟件的好方式戏锹,無論是手機(jī)冠胯、桌面、服務(wù)器還是其他運(yùn)行代碼的軟件锦针。它是一種安全荠察、快速、交互性強(qiáng)的編程語言奈搜,融合了現(xiàn)代語言思維的精華和蘋果工程文化的智慧悉盆,以及蘋果開源社區(qū)的各種貢獻(xiàn)。編譯器針對性能進(jìn)行了優(yōu)化馋吗,語言針對開發(fā)進(jìn)行了優(yōu)化焕盟,兩者都沒有妥協(xié)。

Swift對新程序員很友好宏粤。它是一種工業(yè)質(zhì)量的編程語言脚翘,與腳本語言一樣具有表現(xiàn)力和趣味性。在playground上編寫Swift代碼可以讓你體驗(yàn)代碼并立即看到結(jié)果商架,而不用承擔(dān)構(gòu)建和運(yùn)行應(yīng)用程序的開銷堰怨。

Swift通過采用現(xiàn)代編程模式定義了大量的常見編程錯誤:

  • 變量在使用之前總是要初始化。
  • 檢查數(shù)組索引是否有越界錯誤蛇摸。
  • 檢查整數(shù)是否溢出备图。
  • 可選的變量確保顯式地處理nil值。
  • 自動管理內(nèi)存
  • 錯誤處理允許從意外錯誤中控制恢復(fù)。

Swift代碼是編譯和優(yōu)化過的揽涮,以獲得最大限度的現(xiàn)代硬件抠藕。語法和標(biāo)準(zhǔn)庫是基于這樣的指導(dǎo)原則設(shè)計(jì)的:顯而易見的編寫代碼的方法也應(yīng)該具有最好的性能。它的安全和速度的結(jié)合使Swift成為一個優(yōu)秀的選擇蒋困,從“Hello, world!”到整個操作系統(tǒng)盾似。

Swift將強(qiáng)大的類型推斷和模式匹配與現(xiàn)代的輕量級語法結(jié)合在一起,允許以清晰而簡潔的方式表達(dá)復(fù)雜的思想雪标。因此零院,代碼不僅更容易編寫,而且更容易閱讀和維護(hù)村刨。

Swift已經(jīng)醞釀了數(shù)年告抄,并不斷發(fā)展新的特性和功能。我們對Swift的目標(biāo)是雄心勃勃的嵌牺。我們迫不及待地想看看你用它創(chuàng)造了什么打洼。

2 版本兼容性

本書描述了Xcode 11.4中包含的Swift默認(rèn)版本Swift 5.2。您可以使用Xcode 11.4來構(gòu)建用Swift 5.2逆粹、Swift 4.2或Swift 4來編寫targets募疮。

當(dāng)你用Xcode11.4構(gòu)建Swift 4和Swift 4.2代碼時,大多數(shù)Swift 5.2的功能也可以用僻弹。也就是說阿浓,以下更改僅適用于使用Swift 5.2或更高版本的代碼:

  • 返回不透明類型的函數(shù)需要Swift 5.1運(yùn)行時。
  • 試一試?表達(dá)式?jīng)]有給已經(jīng)返回可選的表達(dá)式引入額外的可選的級別蹋绽。
  • 大整數(shù)文字的初始化表達(dá)式被推斷為正確的整數(shù)類型搔扁。例如,UInt64(0xffff_ffff_ffff_ffff)會計(jì)算出正確的值蟋字,而不是溢出稿蹲。

用Swift 5.2編寫的target可以依賴于Swift 4.2或Swift 4編寫的target,反之亦然鹊奖。這意味著苛聘,如果您有一個被劃分為多個框架的大型項(xiàng)目,您可以一次將代碼從Swift 4遷移到Swift 5.2忠聚。

3 Swift之旅

根據(jù)傳統(tǒng)设哗,用一種新語言編寫的第一個程序應(yīng)該打印“Hello, world!”“在屏幕上。在Swift中两蟀,這可以在一行中完成:

print("Hello, world!")
// Prints "Hello, world!"

如果你用C或Objective-C編寫代碼网梢,這種語法對你來說很熟悉——在Swift中,這行代碼是一個完整的程序赂毯。對于輸入/輸出或字符串處理等功能战虏,不需要導(dǎo)入單獨(dú)的庫拣宰。在全局作用域編寫的代碼用作程序的入口點(diǎn),因此不需要main()函數(shù)烦感。您也不需要在每條語句的末尾寫上分號巡社。

通過展示如何完成各種編程任務(wù),本指南為您提供了足夠的信息來開始使用Swift編寫代碼手趣。如果你有不懂的地方晌该,也不用擔(dān)心——本書后面的部分會詳細(xì)解釋這一旅程中介紹的所有內(nèi)容。

請注意
為了獲得最好的體驗(yàn)绿渣,在Xcode中將本章作為playground打開朝群。Playgrounds允許您編輯代碼并立即看到結(jié)果。
Download Playground

3.1 簡單的值

使用let創(chuàng)建常量中符,使用var創(chuàng)建變量潜圃。一個常量的值不需要在編譯時被知道,但是必須精確地一次為它賦值舟茶。這意味著您可以使用常量來命名一次確定但在許多地方使用的值。

var myVariable = 42
myVariable = 50
let myConstant = 42

常量或變量的類型必須與要賦值的類型相同堵第。但是吧凉,不必總是顯式地編寫類型。在創(chuàng)建常量或變量時提供一個值踏志,使編譯器可以推斷其類型阀捅。在上面的例子中,編譯器推斷myVariable是一個整數(shù)针余,因?yàn)樗某跏贾凳且粋€整數(shù)饲鄙。

如果初始值沒有提供足夠的信息(或者沒有初始值),通過將其寫入變量后面圆雁,用冒號分隔忍级,指定類型。

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

實(shí)驗(yàn)
創(chuàng)建一個具有顯式Float類型和值4的常量伪朽。

值永遠(yuǎn)不會隱式轉(zhuǎn)換為另一種類型轴咱。如果需要將一個值轉(zhuǎn)換為另一種類型,請顯式地創(chuàng)建所需類型的實(shí)例烈涮。

let label = "The width is "
let width = 50
let labelWidth = label + String(width)

實(shí)驗(yàn)
嘗試從最后一行刪除轉(zhuǎn)換字符串朴肺。誤差是多少?

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

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

實(shí)驗(yàn)
使用\ ()在字符串中包含浮點(diǎn)計(jì)算戈稿,并在問候語中包含某人的名字。

對于占用多個行的字符串讶舰,使用三個雙引號(""")鞍盗。只要它與結(jié)束引號的縮進(jìn)一致需了,就可以刪除每個引用行開始的縮進(jìn)。例如:

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

使用方括號([])創(chuàng)建數(shù)組和字典橡疼,并通過在方括號中寫入索引或鍵來訪問它們的元素援所。最后一個元素后面允許有逗號。

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

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

數(shù)組會隨著添加元素而自動增長欣除。

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

要創(chuàng)建空數(shù)組或字典住拭,請使用初始化語法。

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

如果可以推斷類型信息历帚,則可以將空數(shù)組寫成[]滔岳,將空字典寫成[:]——例如,在為變量設(shè)置新值或向函數(shù)傳遞參數(shù)時挽牢。

shoppingList = []
occupations = [:]

3.2 控制流

使用if和switch來生成條件語句谱煤,使用for-in、while和repeat-while來生成循環(huán)禽拔。條件或循環(huán)變量周圍的圓括號是可選的刘离。需要在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語句中睹栖,條件語句必須是布爾表達(dá)式——這意味著代碼如if score{…}是一個錯誤硫惕,而不是隱含的對零的比較。

可以使用if和let一起處理可能丟失的值野来。這些值表示為可選恼除。可選值要么包含值曼氛,要么包含nil豁辉,以表示缺少值。在值的類型后面寫一個問號(?)來標(biāo)記該值為可選值舀患。

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

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

實(shí)驗(yàn)
更改optionalName為nil徽级。你收到了什么問候?如果optionalName為nil,添加一個else子句來設(shè)置不同的問候語聊浅。

如果可選值為nil灰追,則條件為false,并跳過大括號中的代碼狗超。否則弹澎,可選值將被解包并賦給let之后的常量,這使得解包值在代碼塊中可用努咐。

處理可選值的另一種方法是使用??操作符苦蒿。如果缺少可選值,則使用默認(rèn)值渗稍。

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

Switches支持任何類型的數(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?"

實(shí)驗(yàn)
移除default看看會有這么錯誤

結(jié)果:會報(bào)錯補(bǔ)充:在枚舉里团滥,沒有default又不會報(bào)錯`

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

在執(zhí)行匹配的switch case中的代碼之后报强,程序從switch語句中退出灸姊。執(zhí)行不會繼續(xù)到下一個case,因此不需要在每個case的代碼末尾顯式地中斷switch秉溉。

通過為每個鍵值對提供一對名稱力惯,可以使用for-in迭代字典中的項(xiàng)。字典是一個無序的集合召嘶,因此它們的鍵和值將以任意順序迭代父晶。

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"

實(shí)驗(yàn)
添加另一個變量來跟蹤哪種數(shù)字是最大的,以及最大的數(shù)字是什么弄跌。

使用while重復(fù)代碼塊甲喝,直到條件發(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)中保存索引,以生成索引范圍淳玩。

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

使用. .<創(chuàng)建一個忽略其上值的范圍直撤,并使用…生成包含這兩個值的范圍。

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

3.3 函數(shù)和閉包

使用func來聲明一個函數(shù)凯肋。通過在函數(shù)名后面加上圓括號中的參數(shù)列表來調(diào)用函數(shù)。使用->將參數(shù)名稱和類型與函數(shù)的返回類型分開汽馋。

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

實(shí)驗(yàn)
刪除day參數(shù)。添加一個參數(shù),將今天的特別午餐包含在問候中收苏。

默認(rèn)情況下台诗,函數(shù)使用它們的參數(shù)名作為參數(shù)的標(biāo)簽。在參數(shù)名前寫一個自定義參數(shù)標(biāo)簽铁蹈,或?qū)慱來不使用參數(shù)標(biāo)簽宽闲。

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

使用元組生成復(fù)合值——例如,從函數(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)
// Prints "120"
print(statistics.2)
// Prints "120"

函數(shù)可以嵌套。嵌套函數(shù)可以訪問在外部函數(shù)中聲明的變量沿腰±劳剑可以使用嵌套函數(shù)將代碼組織到一個長或復(fù)雜的函數(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ù)實(shí)際上是閉包的一種特殊情況:后面可以調(diào)用的代碼塊。閉包中的代碼可以訪問在創(chuàng)建閉包的作用域中可用的變量和函數(shù)躲叼,即使閉包在執(zhí)行時處于不同的作用域中—您已經(jīng)在嵌套函數(shù)中看到了這樣的示例芦缰。通過在代碼周圍使用大括號({}),可以編寫沒有名稱的閉包枫慷。用于將參數(shù)和返回類型與主體分隔開让蕾。

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

實(shí)驗(yàn)
重寫閉包以對所有奇數(shù)返回零。

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

有幾種方法可以更簡潔地編寫閉包流礁。如果已經(jīng)知道閉包的類型(比如委托的回調(diào))涕俗,則可以省略其參數(shù)的類型、返回類型或兩者都省略神帅。單語句閉包隱式地返回它們唯一語句的值再姑。

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

可以通過數(shù)字而不是名稱引用參數(shù)——這種方法在非常短的閉包中特別有用。作為函數(shù)最后一個參數(shù)傳遞的閉包可以立即出現(xiàn)在括號之后找御。當(dāng)閉包是函數(shù)的唯一參數(shù)時元镀,可以完全省略括號。

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

3.4 對象和類

使用類后跟類名創(chuàng)建類霎桅。類中的屬性聲明與常量或變量聲明的編寫方式相同栖疑,只是它是在類的上下文中。同樣滔驶,方法和函數(shù)聲明也以同樣的方式編寫遇革。

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

實(shí)驗(yàn)
用let添加一個常量屬性,并添加另一個接受參數(shù)的方法揭糕。

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

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

這個版本的Shape類缺少一些重要的東西:創(chuàng)建實(shí)例時用于設(shè)置類的初始化器著角。使用init創(chuàng)建一個揪漩。

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屬性和初始化器的name參數(shù)。在創(chuàng)建類的實(shí)例時吏口,初始化器的參數(shù)像函數(shù)調(diào)用一樣傳遞奄容。每個屬性都需要分配一個值——要么在其聲明中(如numberOfSides),要么在初始化器中(如name)产徊。

如果需要在釋放對象之前執(zhí)行一些清理昂勒,請使用deinit創(chuàng)建一個deinitializer。

子類包括它們的父類名在它們的類名之后舟铜,用冒號分隔叁怪。類不需要子類化任何標(biāo)準(zhǔn)根類,因此可以根據(jù)需要包含或省略父類深滚。

重寫父類實(shí)現(xiàn)的子類上的方法標(biāo)記為重寫—意外重寫方法奕谭,如果不重寫涣觉,編譯器將檢測為錯誤。編譯器還會檢測帶有override的方法血柳,這些方法實(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()

實(shí)驗(yàn)
創(chuàng)建NamedShape的另一個子類Circle,它接受半徑和名稱作為初始化器的參數(shù)难捌。在Circle類上實(shí)現(xiàn)area()和simpleDescription()方法膝宁。

除了存儲的簡單屬性外,屬性還可以有g(shù)etter和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. 設(shè)置子類聲明的屬性的值。
  2. 調(diào)用父類的初始化方法沃斤。
  3. 更改父類定義的屬性的值圣蝎。使用方法、getter或setter的任何其他設(shè)置工作也可以在此時完成衡瓶。

如果您不需要計(jì)算屬性徘公,但仍然需要提供在設(shè)置新值之前和之后運(yùn)行的代碼,請使用willSet和didSet哮针。只要值在初始化器之外發(fā)生更改关面,就會運(yùn)行您提供的代碼。例如十厢,下面的類確保三角形的邊長始終與正方形的邊長相同等太。

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"

當(dāng)使用可選值時,您可以在方法寿烟、屬性和下標(biāo)等操作之前寫?澈驼。如果?前面的值是nil辛燥,?后面的每個東西和整個表達(dá)式的值都為nil筛武。否則,可選值將被解包裝挎塌,而?作用于未包裝的值徘六。在這兩種情況下,整個表達(dá)式的值都是可選值榴都。

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

3.5 枚舉和結(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

實(shí)驗(yàn)
寫一個函數(shù)竿音,比較兩個秩值的原始值和屎。

默認(rèn)情況下,Swift從0開始分配原始值春瞬,每次遞增1柴信,但您可以通過顯式指定值來改變這種行為。在上面的示例中宽气,Ace被顯式地賦予一個原始值1随常,其余的原始值是按順序分配的。還可以使用字符串或浮點(diǎn)數(shù)作為枚舉的原始類型萄涯。使用rawValue屬性訪問枚舉用例的原始值绪氛。

使用init?(rawValue:)初始化器從原始值生成枚舉的實(shí)例。它返回與原始值匹配的枚舉用例涝影,如果沒有匹配的Rank枣察,則返回nil。

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

枚舉的case值是實(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()

實(shí)驗(yàn)
添加一個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的兩種方式:當(dāng)為hearts常量賦值時,由于該常量沒有明確指定類型片效,所以引用枚舉case的Suit.hearts全名红伦。在switch內(nèi)部,枚舉case通過縮寫形式.hearts來引用淀衣,因?yàn)閟elf的值已經(jīng)知道是一個suit昙读。只要值的類型已知,就可以使用縮寫形式膨桥。

如果枚舉具有原始值蛮浑,則這些值將作為聲明的一部分,這意味著特定枚舉case的每個實(shí)例始終具有相同的原始值只嚣。枚舉cases的另一種選擇是擁有與該case相關(guān)聯(lián)的值——這些值是在創(chuàng)建實(shí)例時確定的沮稚,對于枚舉case的每個實(shí)例,它們可以是不同的册舞≡烫停可以將關(guān)聯(lián)值看作類似于枚舉case實(shí)例的存儲屬性。例如,考慮從服務(wù)器請求日出和日落時間的情況盛杰。服務(wù)器要么響應(yīng)請求的信息挽荡,要么響應(yīng)錯誤的描述。

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

實(shí)驗(yàn)
添加第三個case到ServerResponse和switch

請注意即供,日出和日落時間是如何從ServerResponse值中提取的徐伐,作為與switch cases匹配值的一部分。

使用struct創(chuàng)建結(jié)構(gòu)體募狂。結(jié)構(gòu)體支持許多與類相同的行為办素,包括方法和初始化器。結(jié)構(gòu)體和類之間最重要的區(qū)別之一是祸穷,當(dāng)結(jié)構(gòu)體在代碼中傳遞時性穿,它們總是復(fù)制的(值傳遞),而類是通過引用傳遞的(引用傳遞)雷滚。

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

實(shí)驗(yàn)
編寫一個函數(shù)返回一個數(shù)組需曾,該數(shù)組包含一副牌,每副牌的等級和花色組合各一張牌祈远。

3.6 協(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

實(shí)驗(yàn)
向ExampleProtocol添加另一個需求车份。您需要對SimpleClass和SimpleStructure做哪些更改谋减,以使它們?nèi)匀环蠀f(xié)議?

注意,在SimpleStructure的聲明中使用了mutating關(guān)鍵字來標(biāo)記修改結(jié)構(gòu)體的方法扫沼。SimpleClass的聲明不需要將其任何方法標(biāo)記為mutating出爹,因?yàn)轭惿系姆椒偸强梢孕薷念悺?/p>

使用extension向現(xiàn)有類型添加功能,如新方法和計(jì)算屬性缎除⊙暇停可以使用擴(kuò)展將協(xié)議一致性添加到別處聲明的類型,甚至添加到從庫或框架導(dǎo)入的類型器罐。

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

實(shí)驗(yàn)
為Double類型編寫擴(kuò)展梢为,以添加absoluteValue屬性。

您可以像使用任何其他命名類型一樣使用協(xié)議名稱—例如轰坊,創(chuàng)建具有不同類型但都符合單一協(xié)議的對象集合铸董。當(dāng)處理其類型為協(xié)議類型的值時,協(xié)議定義之外的方法不可用衰倦。

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

即使可變protocolValue的運(yùn)行時類型是SimpleClass袒炉,編譯器也會將其視為給定類型的ExampleProtocol旁理。這意味著您不能意外地訪問類實(shí)現(xiàn)的方法或?qū)傩苑悖粌H僅是其協(xié)議一致性。

3.7 錯誤處理

可以使用任何采用錯誤協(xié)議的類型表示錯誤。

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

使用throw來拋出錯誤驻襟,使用throws來標(biāo)記可以拋出錯誤的函數(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來標(biāo)記可能拋出錯誤的代碼。在catch塊中肥隆,除非您給它起了一個不同的名字既荚,否則錯誤將自動被命名為錯誤。

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

實(shí)驗(yàn)
將打印機(jī)名稱改為“Never have Toner”栋艳,使send(job:toPrinter:)函數(shù)拋出一個錯誤恰聘。

您可以提供多個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"

實(shí)驗(yàn)
添加在do塊中拋出錯誤的代碼。為了讓第一個catch塊處理錯誤矾屯,您需要拋出什么類型的錯誤?第二和第三個blocks怎么樣?

處理錯誤的另一種方法是使用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ù)中所有其他代碼之后執(zhí)行纽绍,就在函數(shù)返回之前執(zhí)行蕾久。無論函數(shù)是否拋出錯誤,代碼都將執(zhí)行拌夏。您可以使用defer相鄰地編寫設(shè)置和清理代碼僧著,即使它們需要在不同的時間執(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)
// Prints "false"

3.8 泛型

在尖括號內(nèi)編寫名稱障簿,使其成為泛型函數(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)皆怕。

// 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關(guān)鍵字可以定義一個限制列表,例如,限制類型實(shí)現(xiàn)某一協(xié)議,或者要求兩個類型相同,或者要求類繼承某個父類.

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

實(shí)驗(yàn)
修改anyCommonElements(_ :_ :)函數(shù)毅舆,使其返回任意兩個序列共有的元素?cái)?shù)組。

在上面的例子中,你可以忽略 where ,在冒號后面只寫協(xié)議名或者類名愈腾。寫法 <T: Equatable>與寫法<T where T: Equatable>作用是相同的.

下一章節(jié):基礎(chǔ)

參考文檔:Swift - About Swift

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末憋活,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虱黄,更是在濱河造成了極大的恐慌悦即,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橱乱,死亡現(xiàn)場離奇詭異辜梳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泳叠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門冗美,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人析二,你說我怎么就攤上這事粉洼。” “怎么了叶摄?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵属韧,是天一觀的道長。 經(jīng)常有香客問我蛤吓,道長宵喂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任会傲,我火速辦了婚禮锅棕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淌山。我一直安慰自己裸燎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布泼疑。 她就那樣靜靜地躺著德绿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪退渗。 梳的紋絲不亂的頭發(fā)上移稳,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音会油,去河邊找鬼个粱。 笑死,一個胖子當(dāng)著我的面吹牛翻翩,可吹牛的內(nèi)容都是我干的都许。 我是一名探鬼主播稻薇,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梭稚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起絮吵,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弧烤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹬敲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇昂,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年伴嗡,在試婚紗的時候發(fā)現(xiàn)自己被綠了急波。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘪校,死狀恐怖澄暮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阱扬,我是刑警寧澤泣懊,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站麻惶,受9級特大地震影響馍刮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窃蹋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一卡啰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧警没,春花似錦匈辱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佛南,卻和暖如春梗掰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗅回。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工及穗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绵载。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓埂陆,卻偏偏與公主長得像苛白,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子焚虱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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

  • 引言 繼續(xù)學(xué)習(xí)Swift文檔购裙,從上一章節(jié):函數(shù),我們學(xué)習(xí)了Swift函數(shù)相關(guān)的內(nèi)容鹃栽,如函數(shù)的定義和使用躏率、函數(shù)參數(shù)、...
    shiyueZ閱讀 1,886評論 0 1
  • 引言 繼續(xù)學(xué)習(xí)Swift文檔民鼓,從上一章節(jié):繼承薇芝,我們學(xué)習(xí)了Swift繼承相關(guān)的內(nèi)容,如繼承的作用丰嘉、重寫父類的方法和...
    shiyueZ閱讀 2,079評論 0 2
  • 引言 繼續(xù)學(xué)習(xí)Swift文檔夯到,從上一章節(jié):結(jié)構(gòu)體和類,我們學(xué)習(xí)了Swift結(jié)構(gòu)體和類相關(guān)的內(nèi)容饮亏,如結(jié)構(gòu)體和類的定義...
    shiyueZ閱讀 1,501評論 0 2
  • 引言 繼續(xù)學(xué)習(xí)Swift文檔耍贾,從上一章節(jié):屬性,我們學(xué)習(xí)了Swift屬性相關(guān)的內(nèi)容路幸,如存儲屬性和計(jì)算屬性set和g...
    shiyueZ閱讀 7,072評論 0 1
  • 引言 繼續(xù)學(xué)習(xí)Swift文檔逼争,從上一章節(jié):閉包,我們學(xué)習(xí)了Swift閉包相關(guān)的內(nèi)容劝赔,如閉包的定義和使用誓焦、閉包的簡寫...
    shiyueZ閱讀 1,400評論 0 0