關(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.0
中Swift
的默認(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!")
如果你寫過C
或Objective-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)容最好在Xcode
的playground
中練習(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 = [:]
控制流
使用if
和switch
創(chuàng)建條件語句矫渔,使用for-in
彤蔽、while
和repeat-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
做隱式比較。
你可以用if
和let
來處理可能為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ì)變成什么? 如果optionalName
是nil
,加一個(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."
}
}
使用self
將name
屬性和構(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ù):radiu
和name
,并且實(shí)現(xiàn)area()
和simpleDescription()
方法躲撰。
除了存儲(chǔ)簡(jiǎn)單的執(zhí)行值之外针贬,屬性還有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"
在屬性perimeter
的setter
方法中拢蛋,新值默認(rèn)名稱為newValue
桦他,也可以在set
之后的括號(hào)里指定新值的名稱。
請(qǐng)注意類EquilateralTriangle
的初始化有三步:
1. 設(shè)置子類聲明的屬性值谆棱。
2. 調(diào)用父類的構(gòu)造函數(shù)快压。
3. 重設(shè)父類中定義的屬性值圆仔。此時(shí)也可以調(diào)用getter
、setter
或其他方法蔫劣。
如果不需要對(duì)屬性值進(jìn)行計(jì)算坪郭,但是仍想監(jiān)測(cè)該屬性被設(shè)置新值之前或之后的話,可以使用willSet
和didSet
脉幢。除了構(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()
方法,使spades
和clubs
返回black
人弓,hearts
和diamonds
返回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
中提取到并與switch
的case
相匹配的。
使用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ù)的返回值為一副完整的紙牌晌区,并且每張牌的rank
和suit
一一對(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è)方法,如何修改SimpleClass
和SimpleStructure
才能使它們?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í)protocolValue
是SimpleClass
類型的朴下,但編譯器還是會(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 Toner
,send(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à)的赎败。