Swift5.2-開篇(中文檔案)
引言
今天,開始系統學習Swift,以前都是零零散散的看看的let和var的區別、泛型,只知道它是一個面向協議且型別安全的語言,效能比OC好多了,也沒有去實踐過,感覺很空虛(內心感受,不要想歪)!開發了那麼久才開始加入Swift,感覺落後了好多(T_T)... 好了,往事不提,從今天開始把它撿起來就好了。覺得對於我而言,最有效的學習方法是:一邊學習官方檔案,一邊來翻譯一遍,印象會更深刻一點(好吧,我承認,這是最沒效率的一種方式T_T,奈何我的記性不是很好,只能用這種方法了)。在學習過程中,也終於搞清楚了Swift裡特有的一些功能的用法,比如元組、可選值和解包:?和!、泛型、If let和If var
如果你已經大概瞭解Swift的基本知識點,那麼請參閱下一章節:基礎
歡迎來到SWIFT
1 關於Swift
Swift是一個編寫軟體的好方式,無論是手機、桌面、伺服器還是其他執行程式碼的軟體。它是一種安全、快速、互動性強的程式語言,融合了現代語言思維的精華和蘋果工程文化的智慧,以及蘋果開源社群的各種貢獻。編譯器針對效能進行了優化,語言針對開發進行了優化,兩者都沒有妥協。
Swift對新程式設計師很友好。它是一種工業質量的程式語言,與指令碼語言一樣具有表現力和趣味性。在playground上編寫Swift程式碼可以讓你體驗程式碼並立即看到結果,而不用承擔構建和執行應用程式的開銷。
Swift通過採用現代程式設計模式定義了大量的常見程式設計錯誤:
- 變數在使用之前總是要初始化。
- 檢查陣列索引是否有越界錯誤。
- 檢查整數是否溢位。
- 可選的變數確保顯式地處理nil值。
- 自動管理記憶體
- 錯誤處理允許從意外錯誤中控制恢復。
Swift程式碼是編譯和優化過的,以獲得最大限度的現代硬體。語法和標準庫是基於這樣的指導原則設計的:顯而易見的編寫程式碼的方法也應該具有最好的效能。它的安全和速度的結合使Swift成為一個優秀的選擇,從“Hello,world!”到整個作業系統。
Swift將強大的型別推斷和模式匹配與現代的輕量級語法結合在一起,允許以清晰而簡潔的方式表達複雜的思想。因此,程式碼不僅更容易編寫,而且更容易閱讀和維護。
Swift已經醞釀了數年,並不斷髮展新的特性和功能。我們對Swift的目標是雄心勃勃的。我們迫不及待地想看看你用它創造了什麼。
2 版本相容性
本書描述了Xcode 11.4中包含的Swift預設版本Swift 5.2。您可以使用Xcode 11.4來構建用Swift 5.2、Swift 4.2或Swift 4來編寫targets。
當你用Xcode11.4構建Swift 4和Swift 4.2程式碼時,大多數Swift 5.2的功能也可以用。也就是說,以下更改僅適用於使用Swift 5.2或更高版本的程式碼:
- 返回不透明型別的函式需要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,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,100,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,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,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類的初始化器有三個不同的步驟:
- 設定子類宣告的屬性的值。
- 呼叫父類的初始化方法。
- 更改父類定義的屬性的值。使用方法、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,3],[3])
複製程式碼
實驗 修改anyCommonElements(_ :_ :)函式,使其返回任意兩個序列共有的元素陣列。
在上面的例子中,你可以忽略 where,在冒號後面只寫協議名或者類名。寫法 <T: Equatable>與寫法作用是相同的.