Swift Introduction

Table of Contents

1 The Basics

Swift 是 2014 年由蘋果公司在其開發者大會 WWDC 上所發佈的程式語言

The Swift Programming Language 一書中提到1

  1. Swift 用來撰寫 iOS 及 Mac OSX 的程式。
  2. Swift 吸收了 C 和 Objective-C 的優點,使其在設計更具彈性、容易,且撰寫時有更多樂趣。
  3. Swift 是基於成熟而且倍受喜愛的 Cocoa 和 Cocoa Touch framework,意即 Swift 可以直接使用這些現有的框架
  4. Swift 兼具編譯語言的性能,以及直譯式語言的特性。

2 Learning Resources

3 Swift 常數與變數

3.1 宣告常數和變數

常數和變數必須在使用前宣告,用 let 來宣告常數,用 var 來宣告變數。下面的範例展示了如何用常數和變數來記錄使用者嘗試登錄的次數:

1: let maximumNumberOfLoginAttempts = 10
2: var currentLoginAttempt = 0

你可以在一行中宣告多個常數或者多個變數,用逗號隔開:

1: var x = 0.0, y = 0.0, z = 0.0
2: var population = 8_000_000
3: x = 3
4: y = 4

3.2 Swift v.s. C/C++

Swift 的變數宣告與 C/C++的差異之一在於宣告時不必要先宣告變數型別,例如,同樣要宣告一小數變數與整數變數,C++的做法為

1: int x = 20;
2: double pi = 3.1416;

而 Swift 的寫法為:

1: var x = 20
2: var pi = 3.1416

3.3 Swift is a type-safe language

We declared the variable numOfStudent and assigned 42 (第1行) as its initial value, Swift will assigned it the type integer. We can’t change its type: it will always be an integer.

1: var numOfStudent = 42
2: numOfStudent = 30
3: numOfStudent = 25.8
4: numOfStudent = "fourty" // wrong

3.4 常數與變數的命名

我們可以使用喜歡的字元作為常數和變數名,包括 Unicode 字元:

1: let π = 3.14159
2: let 你好 = "你好世界"
3: let 🐶🐮 = "dogcow"
4: π = 3.14 // try to change the value of constant and find out what happen

但 Swift 的變數亦有其限制,包括:常數與變數名不能包含數學符號、箭頭、保留的(或者非法的)Unicode 碼位、連線與制表字元(box-drawing characters),也不能以數字開頭,但是可以在常數與變數名的其他地方包含數字。

3.5 變數與常數的輸出

1: var x = 20
2: var pi = 3.1416
3: print(x)
4: print(pi)
20
3.1416

3.6 變數與常數的基本規範

  • 使用前需先宣告
  • 不能重複宣告
1: var x = 30
2: let y = 10
3: // invalid redeclaration of variable
4: var x = 40
5: let y = 5

3.7 Stored variable v.s. Computed variable

3.7.1 computed variable

1: var x: Double = 4.3
2: var y: Double = 3.4
3: var z: Double {
4:     return x*y
5: }
6: print(z)
7: x = 10
8: y = 10
9: print(z) //自動重新計算z值
14.62
100.0

3.7.2 contentView 裡的 body 就是 computed variable

1: var body: som View {
2:     return Text("Hello world")
3: }

4 基本運算

4.1 指派運算子

指派運算(a = b),表示用 b 的值來初始化或更新 a 的值:

1: let b = 10
2: var a = 5
3: a = b
4: // a 現在等於 10
5: 
6: let decimalInteger = 17
7: let binaryInteger = 0b10001       // 17 in binary notation
8: let octalInteger = 0o21           // 17 in octal notation
9: let hexadecimalInteger = 0x11     // 17 in hexadecimal notation

4.2 數值運算子

4.2.1 Swift 支援所有數值型別基本運算:

  • 加法(+)
  • 減法(-)
  • 乘法(*)
  • 除法(/)
  • 餘數(%)
1: print(1 + 2)       // 等於 3
2: print(5 - 3)       // 等於 2
3: print(2 * 3)       // 等於 6
4: print(10.0 / 2.5)  // 等於 4.0
5: print(10 / 3)      // 等於 1
6: print(10 & 3)      // 等於 1
3
2
6
4.0
3
2

4.2.2 不同數值的運算

"exports both"body

4.2.3 加法運算子也用於 String 的拼接:

1: "hello, " + "world"  // 等於 "hello, world"
<stdin>:1:11: warning: result of operator '+' is unused
"hello, " + "world"  // 等於 "hello, world"

4.3 複合指派運算子(Compound Assignment Operators)

1: var a = 1
2: a += 2 // a 現在是 3 (ref:+=)

上述程式第2行中的 a += 2 為 a = a + 2 的另一種寫法,其他運算子如-、*、/、%皆可套用同樣的簡寫方式。

4.4 邏輯運算子

邏輯運算的操作物件是邏輯布林值。Swift 支援基於 C 語言的三個標準邏輯運算。

  • 邏輯非(!a)
  • 邏輯且(a && b)
  • 邏輯或(a || b)

5 Property Observers

5.1 Set/Get

在以往的經驗中,對變數進行 assign 是一件直覺而簡單的事,例如:

1: var x: Int = 4
2: var y: Int = 5
3: var z: Int
4: 
5: z = x + y
6: print(z)
9

類似 C#,在 Swift 中,我們卻能透過 Set/Get 來介入 assign 的過程,例如:

 1: var x: Int = 4
 2: var y: Int = 5
 3: var z: Int {
 4:     get { return x + y }
 5:     set { y = newValue - x }
 6: }
 7: 
 8: print(z)
 9: z = 30
10: print("x新值: \(x), y新值: \(y)")
9
x 新值: 4, y 新值: 26

如上例中,我們可以夾帶一些程式碼,在有人賦予 z 新值時(z=?)利用 set(第5行)觸發這些程式碼;同理,也可以在有人讀取其值時觸發。而 newValue 代表被 assign 的新值。

又如下例,某學生的期中考三科成績就可以在輸入成績後自行判斷各科是否及格

 1: class MidExam {
 2:     var id: String = ""
 3:     var chPass = false
 4:     var ch: Int = 0 {
 5:         didSet {
 6:             if ch >= 60 {chPass = true}
 7:         }
 8:     }
 9:     var maPass = false
10:     var ma: Int = 0 {
11:         didSet {
12:             if ma >= 60 {maPass = true}
13:         }
14:     }
15: }
16: 
17: let stsc = MidExam()
18: stsc.id = "202010101"
19: stsc.ch = 70
20: stsc.ma = 32
21: 
22: print(stsc.chPass)
23: print(stsc.maPass)
true
false

上述程式第511行中的 didSet 會在變數被 assign 新值時觸發執行,而 willSet 則是在變數即將被 assign 時觸發。

5.2 willSet/set/didSet

  • 使用 set 時,因為變數的值正在被改變,所以有 newValue 來儲存新值;
  • 使用 willSet 則變數尚未被改變;
  • 使用 didSet 則是變數已被改變,所以有 oldValue

根據 apple 的說法,willSet 和 didSet 不會在 init 中被呼叫:The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.2

6 控制流程

6.1 Conditional Statement

The following examples were from docs.swift.org.

6.1.1 if

In its simplest form, the if statement has a single if condition. It executes a set of statements only if that condition is true.

  1. single condition
    1: var temperatureInFahrenheit = 30
    2: if temperatureInFahrenheit <= 32 {
    3:     print("It's very cold. Consider wearing a scarf.")
    4: }
    
  2. combined conditions
    1: var mathScore = 91
    2: if mathScore <= 100 && mathScore >= 90 {
    3:     print("Grade A")
    4: }
    

6.1.2 if-else

The if statement can provide an alternative set of statements, known as an else clause, for situations when the if condition is false.

1: temperatureInFahrenheit = 40
2: if temperatureInFahrenheit <= 32 {
3:     print("It's very cold. Consider wearing a scarf.")
4: } else {
5:     print("It's not that cold. Wear a t-shirt.")
6: }

6.1.3 if-else if

You can chain multiple if statements together to consider additional clauses.

1: temperatureInFahrenheit = 90
2: if temperatureInFahrenheit <= 32 {
3:     print("It's very cold. Consider wearing a scarf.")
4: } else if temperatureInFahrenheit >= 86 {
5:     print("It's really warm. Don't forget to wear sunscreen.")
6: } else {
7:     print("It's not that cold. Wear a t-shirt.")
8: }

6.1.4 switch

In its simplest form, a switch statement compares a value against one or more values of the same type.

1: switch some value to consider {
2: case value 1:
3:     respond to value 1
4: case value 2,
5:      value 3:
6:     respond to value 2 or 3
7: default:
8:     otherwise, do something else
9: }

This example uses a switch statement to consider a single lowercase character called someCharacter:

1: let someCharacter: Character = "z"
2: switch someCharacter {
3: case "a":
4:     print("The first letter of the alphabet")
5: case "z":
6:     print("The last letter of the alphabet")
7: default:
8:     print("Some other character")
9: }
 1: // 把分數轉成等第
 2: // A(90-100), B(80-90), C(70-80), D(60-70), E(60-)
 3: import Foundation
 4: 
 5: let str = readLine()    //從鍵盤讀入一字串, 存到str中
 6: let choice = Int(str!)   //把str轉成整數,存入score
 7: 
 8: switch choice! {
 9: case 90..<101:
10:     print("A")
11: case 80..<90:
12:     print("B")
13: case 70..<80:
14:     print("C")
15: default:
16:     print("E")
17: }
18: 

6.2 For-In Loops

6.2.1 語法

1: for counter in lower...upper{
2:     your codes
3: }

6.2.2 DEMO

1: for index in 1...5 {
2:     print("\(index) times 5 is \(index * 5)")
3: }
1 times 5 is 5
2 times 5 is 10
3 times 5 is 15
4 times 5 is 20
5 times 5 is 25
1: let base = 3
2: let power = 10
3: var answer = 1
4: for _ in 1...power {
5:     answer *= base
6: }
7: print("\(base) to the power of \(power) is \(answer)")
3 to the power of 10 is 59049

6.3 While Loops

A while loop performs a set of statements until a condition becomes false. These kinds of loops are best used when the number of iterations is not known before the first iteration begins. Swift provides two kinds of while loops:

  • while evaluates its condition at the start of each pass through the loop.
  • repeat-while evaluates its condition at the end of each pass through the loop.

6.3.1 while

A while loop starts by evaluating a single condition. If the condition is true, a set of statements is repeated until the condition becomes false.

  1. 語法
    1: while condition {
    2:     your codes here
    3: }
    
  2. DEMO
    1: var counter = 5
    2: while counter > 0 {
    3:     print("Hello, \(counter)")
    4:     counter -= 1
    5: }
    
    Hello, 5
    Hello, 4
    Hello, 3
    Hello, 2
    Hello, 1
    

6.3.2 repeat … while

The other variation of the while loop, known as the repeat-while loop, performs a single pass through the loop block first, before considering the loop’s condition. It then continues to repeat the loop until the condition is false.

  1. 語法
    1: repeat {
    2:     your codes here
    3: } while condition
    
  2. DEMO
    1: var counter = 5
    2: repeat {
    3:     print("Hello, \(counter)")
    4:     counter -= 1
    5: } while counter > 0
    
    Hello, 5
    Hello, 4
    Hello, 3
    Hello, 2
    Hello, 1
    

7 集合型別

Swift provides three primary collection types, known as arrays, sets, and dictionaries, for storing collections of values. Arrays are ordered collections of values. Sets are unordered collections of unique values. Dictionaries are unordered collections of key-value associations.3

CollectionTypes_intro_2x.png

Figure 1: Collection Types of Swift

7.1 陣列(Arrays)

陣列使用有序列表儲存同一型別的多個值。相同的值可以多次出現在一個陣列的不同位置中。

7.1.1 陣列建構語法

要建立一個空的整數陣列:

1: var EnglishScores = [Int]()
2: print(EnglishScores)
3: // OR
4: var NameArray = [String]()
[]

下面這個範例創建了一個叫做 shoppingList 並且儲存字串的陣列:

1: var shoppingList: [String] = ["Eggs", "Milk"]
2: // shoppingList 已經被建構並且擁有兩個初始項。

7.1.2 存取和修改陣列

我們可以通過陣列的方法和屬性來存取和修改陣列,或者下標語法。 還可以

  • 使用陣列的唯讀屬性 count 來獲取陣列中的資料項數量
  • 使用布林項 isEmpty 來作為檢查 count 屬性的值是否為 0
  • 使用 append 方法在陣列後面添加新的資料項
  • +=
  • remove(at: index)
 1: var shoppingList: [String]  = ["Eggs", "Milk"]
 2: print(shoppingList.count)
 3: print(shoppingList.isEmpty)
 4: shoppingList += ["Bird"]  // Not += "Bird"
 5: shoppingList.append("Fish")
 6: print(shoppingList)
 7: print(shoppingList[0])
 8: shoppingList += ["Beer"]
 9: print(shoppingList)
10: shoppingList.remove(at: 1)
11: print(shoppingList)
2
false
["Eggs", "Milk", "Bird", "Fish"]
Eggs
["Eggs", "Milk", "Bird", "Fish", "Beer"]
["Eggs", "Bird", "Fish", "Beer"]

7.1.3 以 for 遍歷陣列

1: var shoppingList: [String]  = ["Eggs", "Milk", "Beer"]
2: for index in 0...2 {
3:     print(shoppingList[index])
4: }
5: for item in shoppingList {
6:     print(item)
7: }
Eggs
Milk
Beer
Eggs
Milk
Beer

7.1.4 以 repeat 產生 array

If you need to create an array of a specific size holding some default values, Swift has a built-in initializer called repeating:count:. You tell it what to repeat, and how often, and Swift will generate an array of that size.4

1: let nums1 = [Int](repeating: 0, count: 10)
2: let str1 = [String](repeating: "TNFSH", count: 5)
3: print(nums1)
4: print(str1)
5: 
6: let nums2 = [[Int]](repeating: [Int](repeating: 6, count: 3), count: 2)
7: print(nums2)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
["TNFSH", "TNFSH", "TNFSH", "TNFSH", "TNFSH"]
[[6, 6, 6], [6, 6, 6]]

7.1.5 Modify array

  1. map: 批次 update array

    通常我們會使用到 map 函數的原因,主要是要將陣列的內容進行批次變更5,如:

     1: let origin = ["James", "Vanessa", "Ruby", "Letranger"]
     2: let lowercaseVersion = origin.map { $0.lowercased() }
     3: print(lowercaseVersion)
     4: 
     5: let letterCounts = origin.map { $0.count }
     6: print(letterCounts)
     7: 
     8: let nums = [0, 1, 2, 3, 4, 5]
     9: let doubleNums = nums.map { (number) -> Int in
    10:     number * 2
    11: }
    12: print(doubleNums)
    13: 
    14: let doubleStrings = nums.map { (number) -> String in
    15:     "2 * \(number) = \(number * 2)"
    16: }
    17: print(doubleStrings)
    
    ["james", "vanessa", "ruby", "letranger"]
    [5, 7, 4, 9]
    [0, 2, 4, 6, 8, 10]
    ["2 * 0 = 0", "2 * 1 = 2", "2 * 2 = 4", "2 * 3 = 6", "2 * 4 = 8", "2 * 5 = 10"]
    

7.1.6 flatMap()

flatMap 其實就是 flat + map 的結果。而 flatMap 最常是用來處理攤平某個陣列的狀況5

1: let nums = [2, 3, 4]
2: let mapped = nums.map { Array(repeating: $0, count: $0) }
3: print(mapped)
4: 
5: let flatMapped = mapped.flatMap{$0}
6: print(flatMapped)
7: 
[[2, 2], [3, 3, 3], [4, 4, 4, 4]]
[2, 2, 3, 3, 3, 4, 4, 4, 4]

上述程式中第5行的效果與 Array(nums.map(transform).joined())相同。

7.1.7 compactMap

compactMap 所映射出的元素結果都是非可選(non-nil)的值。因此,當你的希望映射結果的陣列是非可選類型的,那麼使用 compactMap 是一個適合的做法5

1: let possibleNums = ["1", "2", "three", "///4///", "5"]
2: let mapped = possibleNums.map { str in Int(str) }
3: print(mapped)
4: 
5: let comMapped = possibleNums.compactMap { str in Int(str) }
6: print(comMapped)
[Optional(1), Optional(2), nil, nil, Optional(5)]
[1, 2, 5]

7.1.8 Array of Struct

基本的陣列提供了我們儲存大批資料的機制,然而,上述所示範的簡易型陣列彈性略嫌不足,如果我們想要開發一個圖書相關的 app,一開始我們至少需要這樣的陣列:

1: let book = ["X的悲劇", "地獄藍調","東方列車謀殺案","八百萬種死法","血字研究"]

然而,書本至少還要有作者吧,於是,我們可能又會加入作者的陣列:

1: let book = ["X的悲劇", "地獄藍調","東方列車謀殺案","八百萬種死法","血字研究"]
2: let author = ["艾勒里.昆恩", "李查德", "阿嘉莎‧克莉絲蒂","勞倫斯.卜洛克","柯南道爾"]

這種設計方式雖然可行,但是卻有幾個問題:

  1. 書名與作者存在不同陣列中,而且書名與作者並無直接的連結
  2. 當我們要再加入出版年份、出版社、售價…等資訊時,就會發現需要太多的陣列了

Struct 就是一種十分適合用來儲存相關資料的型別,以上述書籍資料為例,我們可以先建立一個這樣的 Struct:

1: struct Book {
2:     var title: String
3:     var author: String
4: }

接下來,再建立一個上述 struct 的陣列,如此一來,不管書籍資料再如何複雜,我們都可以一個陣列來儲存:

 1: struct Book {
 2:     var title: String
 3:     var author: String
 4: }
 5: var books = [
 6:     Book(title:"X的悲劇", author: "艾勒里.昆恩"),
 7:     Book(title:"地獄藍調", author: "李查德"),
 8:     Book(title:"東方列車謀殺案", author: "阿嘉莎‧克莉絲蒂"),
 9:     Book(title:"八百萬種死法", author: "勞倫斯.卜洛克"),
10:     Book(title:"血字研究", author: "柯南道爾")
11: ]
12: // 存取陣列中struct的方式
13: print(books[1].title)
14: print(books[1].author)
地獄藍調
李查德

7.2 集合(Sets)

集合(Set)是 Swift 集合類型(collection types)之一,集合用來存儲類型相同且沒有確定順序唯一的值。

7.2.1 宣告

1: var letrers = Set<Character>()

7.2.2 存取集合

 1: var letters = Set<Character>()
 2: print(letters.count)
 3: letters.insert("a")
 4: print(letters.isEmpty)
 5: letters.insert("b")
 6: letters.insert("c")
 7: letters.insert("d")
 8: letters.remove("c")
 9: for item in letters {
10:     print("\(item)")
11: }
0
false
a
d
b

7.2.3 交集、聯集

1: let oddDigits: Set = [1, 3, 5, 7, 9]
2: let evenDigits: Set = [0, 2, 4, 6, 8]
3: let primeNumbers: Set = [2, 3, 5, 7]
4: 
5: print(oddDigits.union(evenDigits))
6: print(oddDigits.union(evenDigits).sorted())
7: print(oddDigits.intersection(evenDigits).sorted())
8: print(oddDigits.subtracting(primeNumbers).sorted())
9: print(oddDigits.symmetricDifference(primeNumbers).sorted())
[7, 5, 6, 3, 0, 4, 2, 8, 9, 1]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[1, 9]
[1, 2, 9]

7.2.4 學習資源

7.3 字典(Dictionaries)

字典是一種儲存多個相同型別的值的容器。每個值(value)都關聯唯一的鍵(key),鍵作為字典中的這個值資料的識別符號。和陣列中的資料項不同,字典中的資料項並沒有具體順序。我們在需要通過識別符號(鍵)存取資料的時候使用字典,這種方法很大程度上和我們在現實世界中使用字典查字義的方法一樣6

7.3.1 建立字典

1: var namesOfIntegers = [Int: String]()
2: // namesOfIntegers is an empty [Int: String] dictionary
3: namesOfIntegers[16] = "Sixteen"
4: print(namesOfIntegers)
[16: "Sixteen"]

一個字典字面量是一個定義擁有一個或者多個鍵值對的字典集合的簡單語句。一個鍵值對是一個 key 和一個 value 的結合體。在字典字面量中,每一個鍵值對的鍵和值都由冒號分割。這些鍵值對構成一個列表,其中這些鍵值對由方括號包含並且由逗號分割:

1: [key 1: value 1, key 2: value 2, key 3: value 3]
<stdin>:1:6: error: expected ',' separator
[key 1: value 1, key 2: value 2, key 3: value 3]
     ^
    ,
<stdin>:1:2: error: cannot find 'key' in scope
[key 1: value 1, key 2: value 2, key 3: value 3]
 ^~~
<stdin>:1:18: error: cannot find 'key' in scope
[key 1: value 1, key 2: value 2, key 3: value 3]
                 ^~~
<stdin>:1:34: error: cannot find 'key' in scope
[key 1: value 1, key 2: value 2, key 3: value 3]
                                 ^~~

7.3.2 存取字典

1: var autherOfWriting: [String: String] = ["老人與海": "海明威", "異鄉人": "卡謬", "百年孤寂": "馬奎斯", "浪淘沙": "東方白"]
2: print(autherOfWriting)
3: print("一共有\(autherOfWriting.count)本書")
4: print(autherOfWriting.isEmpty)
5: autherOfWriting["鼠疫"] = "卡謬"
6: for (writing, author) in autherOfWriting {
7:     print("\(writing)的作者為\(author)")
8: }
["百年孤寂": "馬奎斯", "浪淘沙": "東方白", "老人與海": "海明威", "異鄉人": "卡謬"]
一共有4本書
false
百年孤寂的作者為馬奎斯
浪淘沙的作者為東方白
鼠疫的作者為卡謬
老人與海的作者為海明威
異鄉人的作者為卡謬

8 Functions

Functions are self-contained chunks of code that perform a specific task. You give a function a name that identifies what it does, and this name is used to “call” the function to perform its task when needed.

Swift’s unified function syntax is flexible enough to express anything from a simple C-style function with no parameter names to a complex Objective-C-style method with names and argument labels for each parameter. Parameters can provide default values to simplify function calls and can be passed as in-out parameters, which modify a passed variable once the function has completed its execution.

8.1 Functions 的定義與呼叫

8.1.1 語法

1: func name(arg1 param1: DataTyp, arg2 param2: DataType) -> returnValue DataType {
2:      your code here
3:      return returnValue
4: }

8.1.2 DEMO

"exports both"body

8.1.3 Functions 的參數與傳回值

Function 可以沒有參數,也可以有很多參數,可以沒有傳回值,也可以有很多傳回值(這點與 C/C++不同)

  1. 沒有參數
    1: func sayHelloWorld() -> String {
    2:     return "hello, world"
    3: }
    4: print(sayHelloWorld())
    
  2. 有很多參數
    1: func greet(person: String, alreadyGreeted: Bool) -> String {
    2:     if alreadyGreeted {
    3:         return "Hello, " + person + " again."
    4:     } else {
    5:         return "Hello, " + person
    6:     }
    7: }
    8: print(greet(person: "Tim", alreadyGreeted: true))
    9: // Prints "Hello again, Tim!"
    

    function 的參數可以有兩種名稱:internal name 與 external name,internal 用於 function 內;external name 提供外部程式呼叫 function 使用。若 function 有一個以上的參數,可以省略第一個參數的名稱,如:

     1: func greet(_ person: String, hiAgain alreadyGreeted: Bool) -> String {
     2:     if alreadyGreeted {
     3:         return "Hello, " + person + " again."
     4:     } else {
     5:         return "Hello, " + person
     6:     }
     7: }
     8: print(greet("Vanessa", hiAgain: true))
     9: print(greet("James", hiAgain: false))
    10: // Prints "Hello again, Tim!"
    11: 
    
  3. 沒有傳回值
    1: func greet(person: String) {
    2:     print("Hello, \(person)!")
    3: }
    4: greet(person: "Dave")
    
  4. 有很多傳回值
    1: func myDiv(dividend: Int, divisor: Int) -> (quotient: Int, remainder: Int) {
    2:     return (dividend / divisor, dividend % divisor)
    3: }
    4: 
    5: let results = myDiv(dividend: 10, divisor: 3)
    6: print("商:\(results.quotient),餘數:\(results.remainder)")
    
    商:3,餘數:1
    

9 Generics

當使用者“不在乎”其資料型別時使用,典型的例子為陣列,陣列可以儲存各式各樣型態的資料:Int, Double, String, Struct…

陣列的定義方式如下

1: strut Array<Element> {
2:     //....
3:     func append(_ element: Element) {...}
4: }

上述程式第3行 append 的參數 Element 即為一“don’t care”型別,代表<Element>裡可以填上 Int, String, Struct…。其正式名稱為 Type Parameter。

10 Types

10.1 Varietities of Types

  • struct
  • class
  • protocol
  • “Dont’ Care” type (generics)
  • enum
  • functions

10.2 Functions as Types

如同 Int 或 Double,function 也可以是一種 type,如:

1: var foo: (Double) -> Void
2: func doSomething(what: () ->)

上述程式中第 1 行的 foo 為一變數,其型別為:輸入一小數變數、不傳回值,第 2 行 function 中的參數 what 的型別為一個沒有輸入、傳回 bool 的變數。

如下例:

 1: func square(operand: Double) -> Double {
 2:     return operand * operand
 3: }
 4: 
 5: var operation = square
 6: let result1 = operation(4)
 7: print(result1)
 8: 
 9: operation = abs
10: let result2 = operation(-3)
11: print(result2)

程式中的第5行中,令 operation 變數之型別為 square 這個 function,於是 operation 就可以如 function 般進行計算;也可以令 operation 為內建的 function(如 abs)

11 Closures

It’s kinda like a function you can assign to a variable.一個能接受參數的變數。

1: let makeGreeting: (String) -> String = { name in
2:     return "Hello, \(name)!"
3: }
4: 
5: let message = makeGreeting("Bob")
6: print(message)
Hello, Bob!

上例中常數makeGreeting就包含了一個closure,作用與function一模一樣。

11.1 closure語法與簡化

1: import Foundation
2: 
3: var simpleClosure: (Int, Int) -> Int
4: simpleClosure = { (a: Int, b: Int) -> Int in
5:     return a + b
6: }
7: print(simpleClosure(3, 4))
7

因為第3行已宣告了參數與return type,故第4行也可以改寫成

1: import Foundation
2: 
3: var simpleClosure: (Int, Int) -> Int
4: simpleClosure = { (a, b) in
5:     return a + b
6: }
7: print(simpleClosure(3, 4))
8: 
7

也可再簡化為:

1: import Foundation
2: 
3: var simpleClosure: (Int, Int) -> Int
4: simpleClosure = {
5:     return $0 + $1
6: }
7: print(simpleClosure(3, 4))
7

11.2 closure as function parameter

既然closure與function作用一樣,那為什麼不直接用function呢? 原因之一是closure可以拿來當成其他function的參數

 1: import Foundation
 2: 
 3: // simpleClosure: take two Int paprameters and return one Int
 4: var simpleAddClosure: (Int, Int) -> Int
 5: simpleAddClosure = {
 6:     return $0 + $1
 7: }
 8: 
 9: var simpleMinusClosure: (Int, Int) -> Int
10: simpleMinusClosure = {
11:     return $0 - $1
12: }
13: func operation(_ a: Int, _ b: Int, op: (Int, Int) -> Int) -> Int {
14:     return op(a, b)
15: }
16: 
17: print(operation(2, 3, op: simpleAddClosure))
18: print(operation(2, 3, op: simpleMinusClosure))
5
-1

11.3 Completion Handler

用closure而不用function的另一個原因是: closureh佷適合拿來當成completion handler。 Since you can move the closure around in your code and call it at a later point. This make closures exceptionally useful as useful as completion handlers, i.e. the code you call when a long-running task has finished.

A common application of a closure is the completion handler. It works roughly like this:7

  • You’re executing a lengthy task in your code, like downloading a file, making a calculation, or waiting for a webservice request
  • You want to execute some code when the lengthy task is completed, but you don’t want to “poll” the task continuously to check if it’s finished
  • Instead, you provide closure to the lengthy task, which it will call when the task is completed

假設有一個輸出closure block,該closure傳入一整數,輸出結果

1: let block: (Int) -> Void = { total in
2:    print("Sum of the first 1000 number is \(total)")
3: }
4: block(20)
Sum of the first 1000 number is 20

現在另外設計一個\( \sum^{1000}_{n=1}{n} \)的function,等加總計算完畢後,就緊接著執行這個block將結果輸出:

 1: let block: (Int) -> Void = { total in
 2:    print("Sum of the first 1000 number is \(total)")
 3: }
 4: 
 5: func sumOfFirstThousandNumber(completionHandler: (Int) -> Void) {
 6:     var total = 0
 7:     for i in 1...1000 {
 8:         total = total + i
 9:     }
10: 
11:     completionHandler(total)
12: }
13: 
14: sumOfFirstThousandNumber(completionHandler: block)
Sum of the first 1000 number is 500500

From Swift 3.x, closure parameters are @nonescaping by default, It means closure will also be executed with the function body if you wanna escape closure execution mark it as @escaping.

Completion handler另一個常見的例子是檔案下載,假設我們要自web下載一個image並將之顯示在View中,程式碼大致如下:

1: let task = URLSession.shared.dataTask(with: URL(string: "http://example.com/cats.jpg")!, completionHandler: { data, response, error in
2:     // Do something with image
3: 
4: })

上例中,先以URLSession對網站提出HTTP request下載cats.jpg,closure completionHandlerdataTask() 函數的參數,會在cats.jpg被下載後執行,這就是所謂的 completion handler

11.4 @nonescaping closures:

參考以下範例:

 1: class ClosureTesting {
 2:     func getUser(completion: (String)->Void) {
 3:         completion("TNFSH")
 4:     }
 5:     func execute() {
 6:         getUser { user in
 7:             print(user)
 8:         }
 9:     }
10: }
11: 
12: let test = ClosureTesting()
13: test.execute()
TNFSH

Class ClosureTesting有一function getUser, getUser會接受一個傳入參數completion,該參數為一closure,接受一個傳入參數(字串),傳回Void,並在geUser中執行所傳入的closure。

Class還有一function execute,該function呼叫getUser,所以要傳入一個closure參數,這個closure接受一個參數並將之輸出。

以上即為non-escaping closure,因為function getuser在execute()執行後就return了。如果getUser裡的closure(completion)執行很久呢?能不能讓一段程式碼在function return後執行?

11.4.1 Multi-Threading

一種方式是利用concurrent queue讓closure在不同thread執行:

 1: import Foundation
 2: 
 3: class ClosureTesting {
 4:     let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
 5:     func getUser(completion: (String)->Void) {
 6:         queue.async {
 7:             // let sepnd some time to fetch the data
 8:             Thread.sleep(forTimeInterval: Double.random(in: 0...2))
 9:             completion("TNFSH")
10:         }
11:     }
12:     func execute() {
13:         getUser { user in
14:             print(user)
15:         }
16:     }
17: }
18: let test = ClosureTesting()
19: test.execute()

這段程式碼中,getUser裡的closure被設定為非同步執行,如此一來,這個function會在closure執行前就return了,這是十分常見的網路行為,例如closure正在下載資料,然而這段程式碼會導致如下錯誤:

<stdin>:6:21: error: escaping closure captures non-escaping parameter 'completion'
        queue.async {
                    ^
<stdin>:5:18: note: parameter 'completion' is implicitly non-escaping
    func getUser(completion: (String)->Void) {
                 ^
<stdin>:9:13: note: captured here
            completion("TNFSH")
            ^

Swift官方文件解釋:

“When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.”

即,為了讓closure能合法「逃脫」,closure必須在前面冠上 *@escaping 。如下:

 1: import Foundation
 2: 
 3: class ClosureTesting {
 4:     let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
 5:     func getUser(completion: @escaping (String)->Void) {
 6:         queue.async {
 7:             // let sepnd some time to fetch the data
 8:             Thread.sleep(forTimeInterval: Double.random(in: 0...2))
 9:             completion("TNFSH")
10:         }
11:     }
12:     func execute() {
13:         getUser { user in
14:             print(user)
15:         }
16:     }
17: }
18: let test = ClosureTesting()
19: test.execute()
20: 
TNFSH

再看看以下的例子,function sortArray接受一個整數陣列參數、並設置一個handler:一個傳入Int傳回Void的closure,function將傳入的陣列進行排序,然後執行handler。 doSomething呼叫sortArray後,傳入一組整數陣列,希望做到的handler是將排序後的Array輸出。

 1: func sortArray(array:[Int], handler: (([Int])->Void)) {
 2:     // step 2
 3:     var sortedArray: [Int] = []
 4:     sortedArray = array.sorted();
 5:     // step 3
 6:     handler(sortedArray)
 7: }
 8: func doSomething() {
 9:     // setp 1
10:     sortArray(array: [50,333,434,2,8,3,54,73,99,83,13]) {(result) in
11:         // step 4
12:         print("Sorted Array: \(result)")
13:     }
14: }
15: doSomething()
Sorted Array: [2, 3, 8, 13, 50, 54, 73, 83, 99, 333, 434]

11.5 @escaping closure:

在前例中,如果我們在function sortArray return後仍需取用array的內容,則就需要將之設為@escaping closure。 即,我們希望在第6行後取得已排序的值進行其他應用,則要將之設定為@escaping closure

 1: var findMaxFromArray: (([Int])-> Void)?
 2: 
 3: func findMaxNumber(array:[Int], handler: @escaping ([Int])->Void) {
 4:     //step 2
 5:     handler(array)
 6:     //setp 3
 7:     findMaxFromArray = handler
 8: }
 9: func doSomething() {
10:     //setp 1
11:     findMaxNumber(array: [11,52,33,34,5,65,7,8,29,10]) {(result) in
12:         let maxNumber = result.max()!
13: 
14:         //step 4
15:         print("Max number from Array: \(maxNumber)")
16:     }
17: }
18: doSomething()
19: //setp 5
20: findMaxFromArray!([100,300,754,222,44,432,535])
Max number from Array: 65
Max number from Array: 754

A Closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.

再看另一個@escaping例子

1: func dataTask(with url: URL,
2: completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

function dataTask有兩個參數: 網址資源url以及closure completionHandler,而closure completionHandler有三個參數:

  • data?: 即所下載的資料本身,如圖片
  • URLResponse?: 包含網路回應資訊的object
  • Error?: 包含錯誤訊息的object,也有可能是一個nil

function參數中的 @escaping 意謂著做為參數傳入dataTask function的closure被允許逃離dataTask,換句話說, 這個closure會在function dataTask結束後才被執行

12 Struct, Class, and Enum

struct, class, and enum are fundamentally important concepts for every iOS developer. It’s difficult to imagine common iOS apps without them. 8

Class 和 Struct 是一種多功能且靈活的構造體,作為程式碼中的構造基礎。你可以使用與常數,變數和函數完全相同的語法來定義屬性和方法,以便為 Class 和 Struct 增加功能9

strudt class
Value type Reference type
Copied when passed or assigned Passed around via pointers
Copy on write Automatically reference counted
Functional programming Object-oriented programming
No inheritance Inheritance (single)
“Free” init initalizes ALL vars “Free” init initializes NO vars
Mutability must be explicitly stated Always mutable
Your “go to” data structure Used in specific circumstances
Everything you’ve seen so far is a struct The ViewModel in MVVM is always a class
   
  • 大多變數均為 struct(除了 View 之外),如 arrays, dictionaries, Ints, Bools, Doubles,
  • functional programming: foucs 在資料的 functionality
  • object-oriented programming: focus 在將資料與其 functionality 封裝(encapsulate)入某個 object
  • single inheritance: 只能繼承自一個 class
  • 支援 multiple inheritance 的語言:C++, Common Lisp, Perl, Python, R, Raku, Tcl10.

12.1 語法

1: struct SomeStructure {
2:     // structure definition goes here
3: }
4: class SomeClass {
5:     // class definition goes here
6: }

12.2 DEMO

12.2.1 宣告

 1: struct Resolution {
 2:     var width = 0
 3:     var height = 0
 4: }
 5: class VideoMode {
 6:     var resolution = Resolution()
 7:     var interlaced = false
 8:     var frameRate = 0.0
 9:     var name: String?
10: }
11: 

12.2.2 Init

用於建立 stuct 或 class,為內部變數進行初始化。

1: struct DiceGme {
2:     init(numberOfDices: Int){
3:         // create a dice with that many dices
4:     }
5: }

12.2.3 Structure and Class Instances

The Resolution structure definition and the VideoMode class definition only describe what a Resolution or VideoMode will look like. They themselves don’t describe a specific resolution or video mode. To do that, you need to create an instance of the structure or class.

1: let someResolution = Resolution()
2: let someVideoMode = VideoMode()

12.2.4 Accessing Properties

You can access the properties of an instance using dot syntax. In dot syntax, you write the property name immediately after the instance name, separated by a period (.), without any spaces:

1: print("The width of someResolution is \(someResolution.width)")
2: // Prints "The width of someResolution is 0"
3: print("The width of someVideoMode is \(someVideoMode.resolution.width)")
4: // Prints "The width of someVideoMode is 0"

You can also use dot syntax to assign a new value to a variable property:

1: someVideoMode.resolution.width = 1280
2: print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
3: // Prints "The width of someVideoMode is now 1280"

12.3 Struct

12.3.1 stored properties和computed properties

 1: struct Person {
 2:     let firstName: String
 3:     let lastName: String
 4: }
 5: 
 6: struct Employee {
 7:     let identity: Person
 8:     var jobTitle: String
 9:     var salary: Double
10: }
11: 
12: let me = Person(firstName: "Letranger", lastName: "Yen")
13: var letranger = Employee(identity: me, jobTitle: "CEO", salary: 3000)
14: 
15: print(me.firstName)
16: print(letranger.identity)
17: print(letranger.jobTitle)
Letranger
Person(firstName: "Letranger", lastName: "Yen")
CEO

Person與Employee下的firstName, lastName, jobTitle都為stored property,也都存了值,會佔用memory,另竹種properties為計算所得,稱computed properties。

 1: struct Person {
 2:     let firstName: String
 3:     let lastName: String
 4: }
 5: 
 6: struct Employee {
 7:     let identity: Person
 8:     var jobTitle: String
 9:     var salary: Double
10: 
11:     var tax: Double {
12:         return salary * 0.05
13:     }
14: }
15: 
16: let me = Person(firstName: "Letranger", lastName: "Yen")
17: var letranger = Employee(identity: me, jobTitle: "CEO", salary: 3000)
18: 
19: print(letranger.tax)
150.0

12.3.2 type properties

computed property和stored property都是instance的属性,数据类型也有自己的属性,这就是type property。例如:

 1: struct Person {
 2:     let firstName: String
 3:     let lastName: String
 4: }
 5: struct Employee {
 6:   static var location = "高雄"
 7:   let identity: Person
 8:   var jobTitle: String
 9:   var salary: Double
10: }
11: print(Employee.location)
高雄

location前面加了static,这就意味着location是Employee这个类型的属性,不用创建instance就可以读取。在实践中有时候会很有用。

12.3.3 property observers

store property是有observer观察者的,willSet会在属性即将更新的时候执行;didSet会在属性刚更新完的时候执行。

 1: struct Person {
 2:     let firstName: String
 3:     let lastName: String
 4: }
 5: 
 6: struct Employee {
 7:     let identity: Person
 8:     static var location = "高雄"
 9:     var jobTitle: String
10:     var salary: Double {
11:         didSet {
12:             if salary >= oldValue * 2 {
13:                 Employee.location = "國外"
14:             }
15:         }
16:     }
17: 
18:     var tax: Double {
19:         return salary * 0.05
20:     }
21: }
22: 
23: let me = Person(firstName: "Letranger", lastName: "Yen")
24: var letranger = Employee(identity: me, jobTitle: "CEO", salary: 3000)
25: 
26: print(letranger.tax)
27: print(Employee.location)
28: letranger.salary = 10000
29: print(Employee.location)
150.0
高雄
國外

12.3.4 Struct中的methods

首先如果一个func定义在struct的外面,那他就是个function,如果定义在struct的里面那他就是method。但因为我们已经有了computed property, 我们在需要大量计算或者需要读写数据库的时候用methods,其他情况一般用computed property就可以了。

 1: struct Birthday {
 2:   var day: Int = 12
 3:   var month: Int = 12
 4:   var year: Double = 1999
 5: 
 6:   func myBirthday() {
 7:   print("I've born in \(year).\(month).\(day)")
 8:   }
 9: }
10: 
11: struct Person {
12:   var firstName: String = "Abboskhon"
13:   var lastName: String = "Shukurullaev"
14:   var phoneNumber: String = 123456
15:   var emailAddress: String = "abbsh24@gmail.com"
16: 
17:   func myInfo() {
18:   print("My name is \(firstName) \(secondName). My phone number is \(phoneNumber) and email address is \(emailAddress)")
19:   }
20: }

12.3.5 Mutating methods

Structs have also got so-called mutating methods that play a role in updating the property values of a structure within an instance method. 8

 1: struct Counter {
 2:   var count: Int = 0
 3: 
 4:   mutating func increment() {
 5:     count += 1
 6:   }
 7:   mutating func increment(by amount: Int) {
 8:     count += amount
 9:   }
10:   mutating func reset() {
11:     count = 0
12:   }
13: }
14: var counter = Counter()   //default is 0
15: counter.increment()       //becomes 1
16: counter.increment(by: 9)  //becomes 10
17: counter.reset()           //reset to 0

12.4 Classes

Classes and structures are very similar, and both can be used to define properties and methods. 8

 1: class Person {
 2:   var firstName: String
 3:   var lastName: String
 4: 
 5:   init(firstName: String, lastName: String) {
 6:     self.firstName = firstName
 7:     self.lastName = lastName
 8:   }
 9: 
10:   var fullName: String {
11:     return "\(firstName) \(lastName)"
12:   }
13: }
14: 
15: let me = Person(firstName: "Joshua", lastName: "Jiang")
16: print(me.fullName)
Joshua Jiang

12.4.1 Struct v.s. Class

  1. init

    不像struct会自动生成一个init让你用,上述代码中的init是必须要写的,而且和struct一样,在init中所有stored property(不懂这个的去看上一篇struct哈)都必须赋初始值。

  2. reference type v.s. value type

    Struct是值类型,class是引用类型。分别是什么意思呢?

     1: class Person {
     2:   var firstName: String
     3:   var lastName: String
     4: 
     5:   init(firstName: String, lastName: String) {
     6:     self.firstName = firstName
     7:     self.lastName = lastName
     8:   }
     9: 
    10:   var fullName: String {
    11:     return "\(firstName) \(lastName)"
    12:   }
    13: }
    14: 
    15: var me = Person(firstName: "Joshua", lastName: "Jiang")
    16: var prince = me
    17: print("\(me.firstName), \(prince.firstName)") // Joshua, Joshua
    18: 
    19: me.firstName = "coolboy"
    20: print("\(me.firstName), \(prince.firstName)") // coolboy, coolboy
    21: 
    
    Joshua, Joshua
    coolboy, coolboy
    

    上述程式第16行將me assign給prince,并没有拷贝整个instance给prince,而是拷贝了指向instance的地址;所以当me的firstName变了,那么prince的自然也会变。

  3. 判定相等or not

    当你比较值类型时,不管是struct还是Int,用==即可 当你比较引用类型时,需要用===,比较的是他们指向的地址是否相同

  4. constant or variable

    我们知道如果你把一个struct复制给constant的时候,你是不能修改他的属性的;但class确可以:

     1: class Person {
     2:   var firstName: String
     3:   var lastName: String
     4: 
     5:   init(firstName: String, lastName: String) {
     6:     self.firstName = firstName
     7:     self.lastName = lastName
     8:   }
     9: 
    10:   var fullName: String {
    11:     return "\(firstName) \(lastName)"
    12:   }
    13: }
    14: struct Employee {
    15:     var jobTitle: String
    16: }
    17: let employee = Employee(jobTitle: "CEO")
    18: employee.jobTitle = "CTO" // error: cannot assign to property
    19: 
    20: let me = Person(firstName: "Letrange", lastName: "Yen")
    21: me.firstName = "TNFSH" // it's OK
    
  5. 如何选择用Class还是Struct

    Struct是用来代表值的,比如距离、名称之类的,用的时候创建不用的时候毁掉,很快的这种,你要用struct;

    Class代表一个对象,像一个学生或者一个城市之类的,通常有超多的属性,一般长时间在内存里,不会随便就毁掉,这时候你当然要用class

12.4.2 Inheritance & Override methods and properties

The biggest difference that structs do not have is hierarchical relations. Classes can have parent classes, that are called superclass, and child classes, that are called subclasses. 8

 1: class Animals {
 2:     func animals() {
 3:         print("Animals are mainly of 3 types: Land Animals, Sea animals, Air Animals")
 4:     }
 5: }
 6: 
 7: class LandAnimals: Animals {
 8:     override func animals() {
 9:         print("Land animals are cats, sheeps, horses.")
10:     }
11: }
12: 
13: class Cat: LandAnimals {
14:     override func animals() {
15:         print("I am a cat and I am a land animal.")
16:     }
17: }

12.5 結構跟類的比較 (Comparing Structures and Classes) 11

12.5.1 在 Swift 中的結構與類有許多相同之處,兩者皆能:

  • 定義屬性來儲存值
  • 定義方法來提供功能
  • 定義下標來提供訪問他們用下標語法的值
  • 定義初始化器來設定他們的初始狀態
  • 可被擴展以擴展其功能,超越預設的實現
  • 符合協議以提供某種標準功能

12.5.2 類具有的附加功能,但結構沒有:

  • 繼承使一個類能夠繼承另一個的特性
  • 類型轉換使我們可以在運行時檢查和解釋類實例的類型
  • 反初始化器允許類的實例釋放它已分配的任何資源
  • 引用計數允許對類實例的多個引用

12.5.3 結構和列舉是值型別 (Structures and Enumerations Are Value Types)

值型別是一種其值在被賦值給變數或常數時被複製,或者在傳遞給函數時被複製。實際上,Swift 的整數、浮點數、布林值、字符串、數組和字典中的所有基本型別都是值型別,並且在幕後實現為結構。所有結構和列舉都是 Swift 中的值型別。這代表著我們創建的任何結構和列舉實例以及它們作為屬性的任何值類型在代碼中傳遞時始終會被複製。11

  1. 範例
     1: struct Resolution {
     2:     var width = 0
     3:     var height = 0
     4: }
     5: 
     6: let hd = Resolution(width: 1920, height: 1080)
     7: var cinema = hd
     8: 
     9: cinema.width = 2048
    10: 
    11: print("cinema is now \(cinema.width) pixels wide")
    12: // Prints "cinema is now 2048 pixels wide"
    13: 
    14: print("hd is still \(hd.width) pixels wide")
    15: // Prints "hd is still 1920 pixels wide"
    

    由上述例子可知,當 cinema 被賦予 hd 當前的值,儲存在 hd 的值被複製到新 cinema 的實例。最後結果兩個擁有相同值但完全不同的實例,所以當修改 cinema.width = 2048 的時候,並不會影響儲存在 hd 中的 width。11

    struct.png

    Figure 2: Struct value type

12.5.4 類是參考型別 (Classes Are Reference Types)

不同於值型別,當參考型別被指定給一個變數或常數,或是傳遞進一個函數的時候,並不會被複製。參考型別非副本,是使用相同存在的實例。11

  1. 範例
     1: class VideoMode {
     2:     var interlaced = false
     3:     var frameRate = 0.0
     4:     var name: String?
     5: }
     6: 
     7: let tenEighty = VideoMode()
     8: 
     9: tenEighty.interlaced = true
    10: tenEighty.name = "1080i"
    11: tenEighty.frameRate = 25.0
    12: 
    13: 
    14: let alsoTenEighty = tenEighty
    15: alsoTenEighty.frameRate = 30.0
    16: 
    17: print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
    18: // Prints "The frameRate property of tenEighty is now 30.0"
    

    由上述例子可知,當 alsoTenEighty 被指定為 tenEighty,並且修改 alsoTenEighty.frameRate = 30.0 時,也會更動到 tenEighty.frameRate 的值。11

    class.png

    Figure 3: Class Reference type

12.6 with v.s. without struct: Why you need struct

12.6.1 狀況一: without struct

 1: //學生1資料
 2: var st1-no = "S01"
 3: var st1-name = "James"
 4: var st1-english = 93
 5: var st1-math = 90
 6: //學生2資料
 7: var st2-no = "S02"
 8: var st2-name = "Vanessa"
 9: var st2-english = 90
10: var st2-math = 99

12.6.2 狀況二: with struct

 1: struct Student {
 2:     var no: String?
 3:     var name: String
 4:     var english = 0.0
 5:     var math = 0.0
 6: }
 7: 
 8: let st1 = Studetn(no: "S01", name: "James", english: 93, math: 92)
 9: let st2 = Studetn()
10: st2.no = "S02"
11: st2.name = "Vanessa"
12: st2.english = 90
13: st2.math =  99

12.7 Inheritance

12.7.1 使用 inheritance 的優點

  1. 不用 inheritance
     1: class Car {
     2:     var topSpeed = 200
     3:     func drive() {
     4:         print("Driving at \(topSpeed)")
     5:     }
     6: }
     7: class FutureCar {
     8:     var topSpeed = 300
     9:     func drive() {
    10:         print("Driving at \(topSpeed)")
    11:     }
    12:     func autoDrive() {
    13:         print("Auto-driving")
    14:     }
    15: }
    16: let myRide = Car()
    17: print(myRide.topSpeed)
    18: myRide.drive()
    19: 
    20: let myNewRide = FutureCar()
    21: myNewRide.drive()
    22: myNewRide.autoDrive()
    
    200
    Driving at 200
    Driving at 300
    Auto-driving
    
  2. 使用 inheritance
     1: class Car {
     2:     var topSpeed = 200
     3:     func drive() {
     4:         print("Driving at \(topSpeed)")
     5:     }
     6: }
     7: class FutureCar: Car {
     8:     override func drive() {
     9:         super.drive()
    10:         print("and rockets boosting at 50")
    11:     }
    12: 
    13:     func autoDrive() {
    14:         print("Auto-driving")
    15:     }
    16: }
    17: let myNewRide = FutureCar()
    18: print(myNewRide.topSpeed)
    19: myNewRide.drive()
    
    200
    Driving at 200
    and rockets boosting at 50
    

12.8 Computer property

 1: class Square {
 2:     var edge = 0.0
 3:     var area: Double {
 4:         get {
 5:             return edge*edge
 6:         }
 7:         set(newArea) {
 8:             edge = newArea.squareRoot()
 9:         }
10:     }
11: }
12: 
13: let square = Square()
14: square.edge = 10
15: print(square.area)
16: square.area = 36
17: print(square.edge)
100.0
6.0

12.9 Structures and Enumerations Are Value Types

1: struct Resolution {
2:   var width = 0
3:   var height = 0
4: }
5: 
6: let hd = Resolution(width: 1920, height: 1080)
7: var cinema = hd

When cinema was given the current value of hd, the values stored in hd were copied into the new cinema instance. The end result was two completely separate instances that contained the same numeric values. However, because they are separate instances, setting the width of cinema to 2048 doesn’t affect the width stored in hd, as shown in the figure below12:

sharedStateStruct_2x.png

Figure 4: Value Types

12.10 Classes Are Reference Types

 1: 
 2: struct Resolution {
 3:   var width = 0
 4:   var height = 0
 5: }
 6: class VideoMode {
 7:     var resolution = Resolution()
 8:     var interlaced = false
 9:     var frameRate = 0.0
10:     var name: String?
11: }
12: 
13: let tenEighty = VideoMode()
14: tenEighty.resolution = hd
15: tenEighty.interlaced = true
16: tenEighty.name = "1080i"
17: tenEighty.frameRate = 25.0
18: 
19: let alsoTenEighty = tenEighty
20: alsoTenEighty.frameRate = 30.0
<stdin>:14:24: error: cannot find 'hd' in scope
tenEighty.resolution = hd
                       ^~

Because classes are reference types, tenEighty and alsoTenEighty actually both refer to the same VideoMode instance. Effectively, they are just two different names for the same single instance, as shown in the figure below12:

sharedStateClass_2x.png

Figure 5: Value Types

12.11 Enumerations

Enumerations are different from structs and classes. Enum is a special Swift type that defines a common type for a group of related values. Then, enums can be interacted with other data types such as a switch, if statements, and others. 8

從 Swift 3 開始,我們的 enum 內設定的情境字串要以小寫字母開頭。

 1: //1 representation of enum
 2: enum DownloadStatus {
 3:     case downloading
 4:     case finished
 5:     case failed
 6:     case cancelled
 7: }
 8: 
 9: var currentStatus = DownloadStatus.downloading
10: 
11: switch currentStatus {
12: case .downloading:
13:     print("Downloading...")
14: 
15: case .finished:
16:     print("Just finished the download...")
17: 
18: case .failed:
19:     print("Failed to download the file...")
20: 
21: case .cancelled:
22:     print("The download is cancelled...")
23: }

此時,你可能會想,為什麼需要使用 enum 來定義多個情況,而不選擇宣告一個包含四個情境項目的 array,如下圖所示:

let downloadStatus = [“downloading”, “finished”, “failed”, “cancelled”]
let currentStatus = downloadStatus[0]

你可以這樣做沒錯,但是如此一來會有兩個缺點,首先,你可能會不知道 downloadStatus[0]代表什麼,除非你引用 downloadStatus array,若是比較 downloadStatus[0]與 DownloadStatus.downloading 這兩種表達方式,很明顯的是後者的可讀性比較高。

其次,因為 currentStatus 是 String 類型,變量可以被賦予任何字符串值,無法將它限制為 “downloading”, “finished”, “failed” 以及 “cancelled”,除非執行一些額外的驗證。反之,如果使用 enum,我們可以將 myDirection 限制在 .downloading、.finished、.failed 或 .cancelled 等四種情境之一,不會出現其他意料之外的情況。 13

12.11.1 Raw Values

Swift的enum可以加raw values,像上述例子,有了raw values,我们完全不需要getStatus这个func了。

1: enum Status: String {
2:   case pending = "审核中"
3:   case approved = "审核通过"
4:   case failed = "审核未通过"
5: }
6: print(Status.pending.rawValue) // 审核中
审核中
  1. 如果raw value的类型是Int的话,你要是不给他们赋值,swift会自动从0一个个赋值;你可以给第一个赋值一个int,那么swift也会把后面逐次加1;
  2. 像这个例子,如果我没有赋值,那么pending的rawValue就是“pending”
  3. 你还可以通过rawValue来获得case,比如let pending = Status(rawValue: “审核中”),但是pending会是个optional你要注意
 1: enum Status: String {
 2:   case pending
 3:   case approved
 4:   case failed(msg: String)
 5: }
 6: 
 7: func review(_ name: String) -> Status {
 8:   if name.count > 10 {
 9:     return .failed(msg: "名字太长了")
10:   }
11:   return .pending
12: }
13: 
14: print(review("dsafs")) // pending
15: review("dsafsdfsdfsd") // failed(msg: "名字太长了")
<stdin>:4:8: error: enum with raw type cannot have cases with arguments
  case failed(msg: String)
       ^
<stdin>:1:14: note: declared raw type 'String' here
enum Status: String {
             ^
<stdin>:1:14: error: 'Status' declares raw type 'String', but does not conform to RawRepresentable and conformance could not be synthesized
enum Status: String {
             ^
Swift.RawRepresentable:2:20: note: protocol requires nested type 'RawValue'; do you want to add it?
    associatedtype RawValue
                   ^
<stdin>:15:1: warning: result of call to 'review' is unused
review("dsafsdfsdfsd") // failed(msg: "名字太长了")

12.12 結構和類之間的選擇 (Choosing Between Structures and Classes)

總的來說,用 class 來定義資料物件的話,就好像是在用雲端共享文件一樣:每個人的螢幕上都會有一份文件可以編輯,但這個文件並沒有存在電腦裡,而是跟雲端的版本連線,所以所有的變動都是直接在雲端版本上更新的。好處是方便,壞處是誰修改了甚麼東西經理不會知道(class 本身沒有帳號功能!)。

用 struct 的話,則是像傳統的離線文件檔案一樣。一開始文件只有經理有,而如果他想要讓手下小美去修改文件的話,他就需要拷貝一份檔案給小美。小美修改完檔案後,必須把它交還給經理,然後經理再決定要不要用修改過的檔案取代原本的文件。14

mvc-1.png

Figure 6: MVC

  • 在 MVC 架構中,Model 包含了 Data Objects 與 Document,其中 Documents 包含所有的資料管理者元件(如 Core Data 的 NSManagedObjectContext,或是任何負責下載、上傳資料物件的網路層元件,如自訂的 NetworkManager)。
  • Document 最好是用 class 來定義,因為它肩負了許多溝通的工作。Data Objects 則是資料的代表,要用 class 或 struct 來定義都可以。
  • 用 class 定義 Data Objects 的話,任何變動只要執行一次就可以了,因為它的實體只會有一個。然而,用 struct 定義的話,則需要將變動手動套用到文件所管理的那份實體,好讓整個 app 都能使用最新的資料。這雖然寫起來較為囉唆,卻讓閱讀與維護更為簡單(可以確定是在哪裡被變更資料)14

12.12.1 選擇原則

  • Choose Structures by Default
  • Use Classes When You Need Objective-C Interoperability
  • Use Classes When You Need to Control Identity
  • Use Structures When You Don’t Control Identity

13 Pattern Matching

13.1 if case

if case你可以当成switch(x) { case …: }的简化版

1: let point = (0, 1)
2: // if case
3: if case (0, 0) = point {
4:   print("0, 0") // 不会运行
5: }

13.2 wildcard

 1: let point = (0, 3, 8)
 2: switch point {
 3: case (_, 0, _):
 4:     print("在y轴")
 5: case (0, _, _):
 6:     print("在x轴")
 7: case (_, _, 0):
 8:     print("在z轴")
 9: case (_, _, _):
10:     print("不在xyz轴")
11: }
12: // 在x轴
在x轴

13.3 for case

for后面也可以跟case,同时也可以赋值

 1: let groupSizes = [1, 5, 4, 6, 2, 1, 3]
 2: for case 1 in groupSizes {
 3:   print("我是1") // 2次
 4: }
 5: 
 6: let names: [String?] = ["Joshua", nil, "Dog"]
 7: for case let name? in names {
 8:     print(name, terminator: " ")
 9: }
10: // Joshua Dog
11: 
我是1
我是1
Joshua Dog

上面的例子第一个只有值是1的时候才会print;第二个是Optional的特殊用法,name?代表不是nil的值,只有在name不是nil的时候才会被print。

13.4 检查数据类型

 1: let array: [Any] = [15, "George", 2.0]
 2: 
 3: for element in array {
 4:     switch element {
 5:     case is String:
 6:         print("\(element)是String")
 7:     case let num as Int:
 8:         print("\(num)是Int")
 9:     default:
10:         print("\(element)是个啥??")
11:     }
12: }
13: // 15是Int
14: // George是String
15: // 2.0是个啥??
15是Int
George是String
2.0是个啥??

13.5 case…where…

case后面还可以再加where进一步的筛选:

 1: for number in 1...9 {
 2:     switch number {
 3:     case let x where x % 2 == 0:
 4:         print("偶数")
 5:     case _ where number % 2 > 0:
 6:         print("奇数")
 7:     default:
 8:         print("QQ")
 9:     }
10: }
奇数
偶数
奇数
偶数
奇数
偶数
奇数
偶数
奇数

13.6 if多个条件

1: var a: Int? = 6
2: if let a = a, a > 5, case 1...9 = a {
3:     print("yes")
4: }

Footnotes:

Author: Yung Chin, Yen

Created: 2021-05-12 Wed 09:12