變量和常量
任何 Swift 中的變量要么不變的,要么是可變的。這句話可不要和 Int、 Float 這些變量類型混淆。變量和常量僅僅是一種視角來描繪持有的值是可修改的(即可變性),亦或是不能修改的(即不可變性)。
要定義一個常量,使用 let 關鍵字。舉例來說:
let name = "Jameson"````
如果你想改變 name 的值,會發現沒有辦法做到,然后 Swift 在編譯時會拋出一個錯誤。
```
let name = "Jameson"
name = "Bob"
error: cannot assign to value: 'name' is a 'let' constant
name = "Bob"
~~~~ ^````
要解決它,我們可以使用 var 關鍵字,來定義一個可以修改的變量:
var name = "Jameson"
name = "Bob"````
這次代碼就不會報錯了。
一般來說,你應該默認去使用 let 關鍵字,除非知道需要使用到 var 關鍵字。這樣的方式將會從根本上增加代碼的安全性。如果當你定義了常量,之后要去修改它時,你會得到錯誤提示,可以到那個時候再決定是不是應該使用 var 關鍵字?;蛘呷珏e誤是給你的提示,應該重新去思考當前的邏輯流。一般來說,不可變性優于可變性,它可以幫助開發者少犯錯誤,并且更容易編寫代碼。
分號
在Swift 并不強制要求你在每條語句的結尾處使用分號( ; ),當然,你也可以按照 你自己的習慣添加分號。有一種情況下必須要用分號,即你打算在同一行內寫多條獨立的語句:
let cat = "?"; print(cat)
// 輸出 "?"
注釋
//單行注釋
/*
多行注釋
*/````
####類型推斷
在Swift中,可以不用顯式的制定數據類型,swift能夠自行推測出數據類型
####斷言
斷言在調試中非常有用,就像別的語言中的斷點調試。Swift通過一個全局函數assert來實現。它有兩個參數:一個表達式,一條信息,若表達式的結果是false就會中斷程序并打印那條信息。
```
var age = 34
assert(age<18, "如果你小于18歲你就不是大叔了")
```
####基礎類型
在 Swift 中,一個類型被聲明的寫法是通過聲明一個變量,然后緊跟一個冒號,然后是類型名稱。例如我們聲明一個整型,在 Swift 中類型是 Int ,那么你可以如下寫法:
let age: Int = 5````
相似的,你可以聲明一個字符串類型:
let name: String = "Jameson"````
Swift 支持類型推斷,可以不寫具體的類型信息。然后讓編譯器根據它的初始值來推斷它是什么類型的。
let age = 5
let name = "Jameson"````
age 和 name 的類型仍然是 Int 和 String ,但是這次我們跳過了類型聲明,因為很顯然,5 是 Int 類型,而 “Jameson” 是一個字符串。
記住, let 關鍵字僅僅使值變得不可變。如果我們預測這個 age 的值是可變的,而 name 不是可變的,那么我們應該這么寫:
var age = 5
let name = "Jameson"````
現在如果要更新 age 的值,可以這么做:
var age = 5
let name = "Jameson"
age = 25
print(age)````
布爾值
Swift的布爾值為Bool,它有兩個布爾常量--true和false。
浮點數
Swift有兩種浮點數類型,64位浮點數的Double和32位的浮點數Float。
數值型類型轉換
let heigth = 1.73 //
let iHeight = Int(heigth) //把height轉成Int類型
let fHeight = Float(heigth) //把height轉成Float類型
let dHeight = Double(heigth) //把height轉成Double類型
使用字符串
用 print 打印命令或者 String 的字符串總是很方便。例如,我想要打印一個包含變量 age 和變量 name 的語句,可以在兩個 String 變量之間用 + 操作符。
let age = "15"
let name = "Robb"
let sentence = name + " is " + age
print(sentence)````
打印結果為:Robb is 15
改用另外一個方式來拼接 String,可以不使用 + 操作符,而是在將每一個變量放進一組括號中,并在變量前使用 \ 反斜杠。
let sentence = "(name) is (age)"
print(sentence)
Robb is 15````
現在也可以看到同樣的效果,但是它更容易閱讀和組合。
也許你注意到了, age 現在是一個 String 類型因為它現在是 “15” 而不是 15,沒有了引號。這是因為如果一個字符串和一個整型組合, Int 類型將不會自動轉型為 String 類型,這在組合前是非常重要的一步。
這樣的話,以下代碼會產生錯誤。
let age = 15
let name = "Robb"
let sentence = name + " is " + age
print(sentence)````
Error: Binary operator '+' cannot be applied to operands
of type 'String' and 'Int'````
因此我們所要做的就是將 age 變成一個 String 類型的。可以通過強制轉型來做到,使用 String 的初始化方法,傳入一個 Int 類型的值作為參數值。
let age = 15
let name = "Robb"
let stringAge = String(age)
let sentence = name + " is " + stringAge
print(sentence)````
打印結果為:Robb is 15
我們創建了一個新的變量叫做 stringAge 。然后使用了類型轉換,因為字符串插值操作會單獨的分析每一個表達式,同樣獲取到圓括號內的內容。
let age = 15
let name = "Robb"
let sentence = name + " is " + String(age)
print(sentence)
print("(name) enjoys being (String(age))")````
打印結果為:
Robb is 15
Robb enjoys being15
可選類型
Swift 中有可選類型的概念。一個可選類型是一個可以為 nil、null 或者是沒有被設置值的變量。一般來說,你可以認為大部分其他編程語言的任何變量都是一個可選類型。一個變量的可選性通過在類型聲明時的類型名稱后面加上問號符號 ? 來聲明。 因此繼續上面的例子,我們知道 age 和 name 總是會被設置,于是我們也許該添加另外一個可能為 nil 的變量。我們來拿 favoriteColor 來做一個例子。許多人都會有最愛的顏色,但可能對一部分人來說卻沒有,或者我們不知道別人的那些數據。因此我們會把它聲明為可選類型,并且不對它進行賦值。
var favoriteColor: String?````
在對可選類型的聲明中如果不進行賦值,那么它就是 nil 的。你可以使用 print 函數來對它進行打印來證實這個觀點。
var favoriteColor: String?
print(favoriteColor)````
打印結果為:nil
我們之后將對 favoriteColor 進行賦值,然后發現它不再是 nil 的。
var favoriteColor: String?
favoriteColor = "Blue"
print(favoriteColor)
Optional("Blue")````
我們發現結果不是 "Blue" ,而是 Optional("Blue") 。那是因為實際值仍然包裹在可選類型之中。
你可以認為可選類型就像一個生日禮物,像禮物盒外面那層精美的包裝紙,拆開他們之后,也許里面什么都沒有。這對某些過生日的人真是個殘忍的禮物,不過這確實真的會發生。也許禮物盒中確實會有真的禮物,可也得拆開并且實際去看才知道,現在它只是一個沒有被拆開,躺在我們手中的一個盒子。
如果我們想知道里面是什么,需要馬上拆開禮物,對可選類型來說也是一樣,當傳遞和使用它們時,實際我們只是在和一個也許有值的容器在打交道。就像禮物一樣,可選類型在被使用之前必須被解包。
Swift 中聲明一個可選類型可以不賦值,編譯也會通過。但是如果我們聲明這些變量時不加上可選類型的符號,那么就會報錯。
var favoriteColor = "Blue"
favoriteColor = nil````
error: nil cannot be assigned to type 'String'````
同樣,非可選類型在聲明的時候也不能被賦值為 nil。
```
var favoriteColor: String```
```
error: variables must have an initial value```
####nil
你可以給可選變量賦值為 nil 來表示它沒有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一個可選的 Int 值 404 serverResponseCode = nil
// serverResponseCode 現在不包含值````
注意:nil 不能用于非可選的常量和變量。如果你的代碼中有常量或者變量需要處理值缺失的情況,請把它們聲明成對應的可選類型。
如果你聲明一個可選常量或者變量但是沒有賦值,它們會自動被設置為 nil :
var surveyAnswer: String?
// surveyAnswer 被自動設置為 nil
注意:Swift 的 nil 和 Objective-C 中的 nil 并不一樣。在 Objective-C 中, nil 是一個指向不存在對象的指 針。在 Swift 中, nil 不是指針——它是一個確定的值,用來表示值缺失。任何類型的可選狀態都可以被設 置為 nil ,不只是對象類型。
解包
我們現在知道可選類型是什么了,它們可以使變量可以為空,也知道與其說它們是值不如說是一個容器。因此,在項目中要訪問可選類型中的內容時,我們該怎么做?
第一,最普遍的方式是使用可選類型綁定,在可選綁定中,你可以在一個 if 語句中把可選類型的值賦給一個新的值。如果可選類型包含一個值,那個新的變量就會被成功設置,并且跟隨 if 語句的代碼閉包也會成功執行。
來看例子,這里將聲明兩個可選類型,一個叫做 favoriteAnimal ,它被設置值為 Fox ,而另外一個是 favoriteSong 我們并沒有對它進行賦值。
var favoriteAnimal: String?
var favoriteSong: String?
favoriteAnimal = "Fox"```
現在我們使用可選綁定來看一看編程變量是否都有值,我們可以打印出包含它們值的語句。首先先來檢查一下 favoriteAnimal。
if let unwrappedFavoriteAnimal = favoriteAnimal {
print("Favorite animal is: " + unwrappedFavoriteAnimal)
}
//打印結果為:Favorite animal is: Fox````
當沒有被設置值時,僅僅會觸發 else 語句,或者如果連 else 語句都沒有,那么什么都不會觸發。
if let unwrappedFavoriteSong = favoriteSong {
print("Favorite song is: " + unwrappedFavoriteSong)
}
else {
print("I don't know what your favorite song is!")
}
//打印結果為:I don't know what your favorite song is!````
如果我們要解包多個可選類型,并且對它們進行邏輯處理,首先要檢查它們:
var favoriteAnimal: String?
var favoriteSong: String?
favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"
if let unwrappedFavoriteSong = favoriteSong {
if let unwrappedFavoriteAnimal = favoriteAnimal {
print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
}
}````
這看上去非常雜亂,因此 Swift 提供一種簡便方式來一次解包多個變量:
var favoriteAnimal: String?
var favoriteSong: String?
favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"
if let unwrappedFavoriteSong = favoriteSong,
let unwrappedFavoriteAnimal = favoriteAnimal {
print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
}````
####集合類
Swift 有好幾種集合類型,最常用的是數組、集合、字典。
**數組**
我們首先來看一下數組的例子。
let starks: [String] = ["Eddard", "Catelyn", "Robb", "Sansa"]````
這里我們定義了一個基本的 Array 類型,它是字符串數組類型 [String]。
這個方括號暗示了它是一個存放字符串對象的數組,而不是一個字符串類型。一般來說,Swift 可以通過檢測所賦的初值進行類型推斷。
let starks = ["Robb", "Sansa", "Arya", "Jon"]````
我們可以有多種方式訪問數組中的元素,比如通過 Int 類型的下標,或者調用各種集合類型的方法。
let starks = ["Robb", "Sansa", "Arya", "Jon"]
print( starks[0] )
print( starks[2] )
print( starks.first! )````
打印結果為:
Robb
Arya
Robb
你應該發現數組是以 0 為下標開始的,因此數組中的第一個元素 "Robb" 可以通過 stack[0] 來訪問。
另外,可能你會發現使用 first 方法返回的是一個可選值。而下標訪問器返回的并不是一個可選值。如果訪問數組中沒有出現的下標,程序將會在運行時報錯。因此在通過下標訪問時檢查數組的長度:
if starks.count >= 4 {
print( starks[3] )
}````
有幾種方式可以自動的檢查這個類型,但是因為一些性能原因它不會默認去做。
**哈希類型/字典**
字典可以存儲鍵值對,鍵的典型類型是字符串類型,但它也可以是 Swift 中的其他各種類型。在下面這個例子中,我們會創建一個基本字典,以字符串為鍵,整型為值。
```
let ages = ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15]```
我們可以訪問這些值通過 String 的鍵
```
print( ages["Arya"]! )
print( ages["Jon"]! )````
打印結果為:10 15
要注意的是,我們解包這些值只是因為它們是可選值,它們有可能為 nil
使用可選綁定來解包字典中的值是較安全的,特別是你認為這些值很有可能為 nil 時
if let aryasAge = ages["Arya"] {
print("Arya is (aryasAge) years old")
}````
打印結果為:Arya is 10 years old
我們也可以把數組存儲在字典中,或者把字典存儲在數組中,或者把他們混合使用。
let families = [
"Stark": ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15],
"Baratheon": ["Joffrey": 13, "Tommen": 8]
]
let tommensAge = families["Baratheon"]!["Tommen"]!
print("Tommen is \(tommensAge) years old")````
打印結果為:Tommen is 8 years old
這個 houses 的類型將會是 [String: [String: Int]]
另外一個角度也可以說,這是一個字符串為鍵,以 [String: Int] 為值的一個字典。
**集合**
Swift3 中的集合和數組很相似,但集合的值是唯一的和無序的。
初始化一個集合看起來就像初始化一個數組,唯一不同的是類型:
let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]````
代碼創建了一個字符串的集合。大于和小于符號 "<"">" 暗示 Swift 中的泛型類型,你可能注意到了 "Blue" 在列表中出現了兩次,但是如果我們把顏色打印出來,馬上就會發現:
let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
print(colors)
///打印結果:["Orange", "Red", "Blue"]````
你也許還注意到了順序也不一致了,因為集合不會維持特定的順序。
我們無法像訪問數組下標一樣的方式去訪問集合。但是可以用集合中內置的方法來增加或者刪除元素,可以通過 contains 方法來查看是否集合中包含了該元素。
var colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
colors.insert("Black")
colors.insert("Black")
colors.remove("Red")
print(colors)
print(colors.contains("Black"))
print(colors.contains("Red"))
["Black", "Orange", "Blue"]
//true
//false````
構造集合對象最常見的方式就是羅列哪些元素應該納入列表,哪些元素應該被排除。
這里還有許多方法我還沒有提到,我建議你去閱讀一下蘋果的官方文檔關于這三種集合類型,這樣就會對它們更了解。
元組
元組并不是一種集合,而應該說是用一個標識符來表示多個不同變量。
let fullName = ("Jameson", "Quave")````
(String, String) 是一個元組類型,我們可以使用點語法來訪問每一個元組的成員,看看下面的情況:
let fullName = ("Jameson", "Quave")
print(fullName.1)
print(fullName.0)
//Quave
//Jameson````
元組也可以用一個新的多個變量名來構造:
let (first, last) = ("Jameson", "Quave")
print(first)
//Jameson````
由于我們沒有用到 last name,可以忽略那個值通過使用下劃線 _ ,并且仍然構造 first name。
let (first, _) = ("Jameson", "Quave")
print(first)
Jameson````
當你在使用方法時想返回多個返回值時,元組會很有用。
控制流
Swift 的控制流比起其他語言要優雅,我們先從 if 和 else 語句這些基本層面著手:
if 10 > 5 {
print("10 is greater than 5.")
}
else {
print("10 is not greater than five.")
}
//10 is greater than 5```
你也可以用括號來包裹 if 語句的條件:
if (10 > 5) {
...
//Swift 也支持 switch 語句,在編譯期的時候檢查你是否已經覆蓋了所有的可能條件,
//如果你沒有覆蓋所有的條件,你得加上 defualt:case 來處理一些沒有考慮到的情況:
let name = "Jameson"
switch(name) {
case "Joe":
print("Name is Joe")
case "Jameson":
print("This is Jameson")
default:
print("I don't know of this person!")
}
//This is Jameson```
由于此處 name 的值是 "This is Jameson"。我們匹配到了第二個條件,然后執行下面這行。
print("This is Jameson")
如果我們把名稱設置為一些之前沒有出現在列舉情況的東西時,比如 "Jason" ,switch 將會自動落入默認的情況:
let name = "Jason"
switch(name) {
case "Joe":
print("Name is Joe")
case "Jameson":
print("This is Jameson")
default:
print("I don't know of this person!")
}
//I don't know of this person!```
####循環和集合類型
Swift3 不再支持你過去所使用的 C 風格的循環,取而代之的是使用枚舉和 for-each 風格的循環,語法是` for element in array`
let names = ["Robb", "Sansa", "Arya", "Jon"]
for name in names {
print("Name: (name)")
}
Name: Robb
Name: Sansa
Name: Arya
Name: Jon```
如果你想要循環整個數組,這個寫法就很棒,沒有 C 風格的數組,如果我們循環遍歷一系列數字呢?Swift 中的 Range 和 Stride 給出了答案,如果想要打印到 10 里面3的倍數,可以使用 Range 通過使用語法 1…10 表示從 1 到 10 。然后我們打印每一個數字,那些數字都被 % 符號除以 3 ,并且檢查它們的余數是不是都是0。
for i in 1...10 {
if i % 3 == 0 {
print(i)
}
}```
輸出結果為:3 6 9
另外一種方式是通過 stride 每隔三個元素訪問一次。stride 可以用很多方法來創建,但是最常見的是 stride(from: , to:, by:),from value 就是跨步訪問的起初值,然后 by 是每隔多少跨步值才能訪問到 to 值。聽起來有點繞,讓我們來看實際的代碼:
let byThrees = stride(from: 3, to: 10, by: 3)
for n in byThrees {
print(n)
}```
輸出結果為:3 6 9
從英語來看十分好讀,你也可以說你從 3 數到 10 每隔3個數。這里我們創造 stride 并且用一個變量 byThrees 來存儲他們的值,但是也可以直接在循環中使用它們。
for n in stride(from: 3, to: 10, by: 3) {
print(n)
}````
//輸出結果為3 6 9
集合都有一個 indices 屬性用于循環中使用,它會返回一個集合的下標數組,非常適合訪問或者過濾集合中某些元素的情況。這里回到我們的名稱集合的例子,如果想要前三個名稱,可以這樣寫:
let names = ["Robb", "Sansa", "Arya", "Jon"]
for nameIndex in names.indices {
if(nameIndex < 3) {
print(names[nameIndex])
}
}
Robb, Sansa, Arya````
在集合中還有枚舉的方法,它允許你通過遍歷下標和值:
let names = ["Robb", "Sansa", "Arya", "Jon"]
for (index, name) in names.enumerated() {
print("\(index): \(name)")
}
0: Robb
1: Sansa
2: Arya
3: Jon````
在 Swift3 中還有很多方式來遍歷對象,但他們通常不是很常用。
也許你已經發現我們的循環中同時給兩個變量賦值,index 和 name。它們被用逗號分隔并被括號括起來,表示我們從 enumerated() 返回的兩個被命名的變量。
####函數和閉包
使用`func`來聲明函數,使用名字和參數來調用函數。使用->來指定函數返回值的類型。
//聲明creat
方法,傳入person
和day
兩個參數,返回一個String
類型的值
func creat(person: String, day: String) -> String {
return "Hello,(person),today is (day)."
}
//使用creat
函數調用生成一個字符串,并賦值給result
let result = creat(person: "小王", day: "星期八")
默認情況下,函數使用他們的參數作為他們的參數的標簽,在參數名稱前可以自定義參數標簽,或者使用\`_`表示不適用參數標簽。
//聲明函數
func creat1(_ person: String, day: String) -> String {
return "Hello,(person),today is (day)."
}
//調用方式
let result1 = creat1( "小王", day: "星期八")
print(result1);
使用元組來讓一個函數返回多個值,元組的元素可以用名稱和數字來表示。
//聲明一個函數
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)
print(statistics.2)
函數可以嵌套。被嵌套的函數可以訪問外側函數的變量,你可以使用嵌套函數來重構一個太長或者太復雜的函
數。
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, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
函數實際上是一種特殊的閉包:它是一段能之后被調取的代碼。閉包中的代碼能訪問閉包所建作用域中能得到的變 量和函數,即使閉包是在一個不同的作用域被執行的 - 你已經在嵌套函數例子中所看到。你可以使用 {} 來創建 一個匿名閉包。使用` in `將參數和返回值類型聲明與閉包函數體進行分離。
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})````
對象和類
使用 class 和類名來創建一個類。類中屬性的聲明和常量、變量聲明一樣,唯一的區別就是它們的上下文是 類。同樣,方法和函數聲明也一樣。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
要創建一個類的實例,在類名后面加上括號。使用點語法來訪問實例的屬性和方法。
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
被用來區別實例變量。當你創建實例的時候,像傳入函數參數一樣給類傳入構造器的參數。每個屬性都 需要賦值——無論是通過聲明(就像 numberOfSides )還是通過構造器(就像 name )。
如果你需要在刪除對象之前進行一些清理工作,使用 deinit
創建一個析構函數。
子類的定義方法是在它們的類名后面加上父類的名字,用冒號分割。創建類的時候并不需要一個標準的根類,所以你可以忽略父類。
子類如果要重寫父類的方法的話,需要用 override 標記——如果沒有添加 override 就重寫父類方法的話編譯器 會報錯。編譯器同樣會檢測 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()
除了儲存簡單的屬性之外,屬性可以有 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 triagle with sides of length \(sideLength)."
} }
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
在 perimeter 的 setter 中,新值的名字是 newValue 。你可以在 set 之后顯式的設置一個名字。 注意 EquilateralTriangle 類的構造器執行了三步:
- 設置子類聲明的屬性值
- 調用父類的構造器
- 改變父類定義的屬性值。其他的工作比如調用方法getters 和 setters 也可以在這個階段完成。
如果你不需要計算屬性,但是仍然需要在設置一個新值之前或者之后運行代碼,使用 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)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
處理變量的可選值時,你可以在操作(比如方法、屬性和子腳本)之前加 ? 。如果 ? 之前的值是 nil , ? 后面 的東西都會被忽略,并且整個表達式返回 nil 。否則, ? 之后的東西都會被運行。在這兩種情況下,整個表達式 的值也是一個可選值。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
枚舉和結構體
使用 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 的方式為原始值進行賦值,不過你可以通過顯式賦值進行改變
使用init?(rawValue:)
初始化構造器在原始值和枚舉值之間進行轉換。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
枚舉的成員值是實際值,并不是原始值的另一種表達方法。實際上,如果沒有比較有意義的原始值,你就不需要
提供原始值。
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()
注意,有兩種方式可以引用 Hearts 成員:給 hearts 常量賦時,枚舉成員 Suit.Hearts 需要用全名來引用,因 為常量沒有顯式指定類型。在 switch 里,枚舉成員使用縮寫 .Hearts 來引用,因為 self 的值已經知道是一個 it 。已知變量類型的情況下你可以使用縮寫。
一個枚舉成員的實例可以有實例值。相同枚舉成員的實例可以有不同的值。創建實例的時候傳入值即可。實例值和原始值是不同的:枚舉成員的原始值對于所有實例都是相同的,而且你是在定義枚舉的時候設置原始值。
使用 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()
協議和擴展
使用 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
注意聲明 SimpleStructure 時候 mutating 關鍵字用來標記一個會修改結構體的方法。 SimpleClass 的聲明不需要 標記任何方法,因為類中的方法通??梢孕薷念悓傩?類的性質)。
使用 extension 來為現有的類型添加功能,比如新的方法和計算屬性。你可以使用擴展在別處修改定義,甚至是 從外部庫或者框架引入的一個類型,使得這個類型遵循某個協議。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42 }
}
print(7.simpleDescription)
你可以像使用其他命名類型一樣使用協議名——例如,創建一個有不同類型但是都實現一個協議的對象集合。當
你處理類型是協議的值時,協議外定義的方法不可用。
let protocolValue: ExampleProtocol = a print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // 去掉注釋可以看到錯誤
即使 protocolValue 變量運行時的類型是 simpleClass ,編譯器會把它的類型當做 ExampleProtocol 。這表示你不 能調用類在它實現的協議之外實現的方法或者屬性。
錯誤處理
使用采用 Error
協議的類型來表示錯誤。
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 代碼塊中,除非你另外命名,否則錯誤會自動命名為 error 。
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
可以使用多個 catch 塊來處理特定的錯誤。參照 switch 中的 case 風格來寫 catch 。
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)
}
另一種處理錯誤的方式使用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)
泛型
在尖括號里寫一個名字來創建一個泛型函數或者類型。
func repeatItem<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
repeatItem(repeating: "knock", numberOfTimes:4)
你也可以創建泛型函數、方法、類、枚舉和結構體。
// 重新實現 Swift 標準庫中的可選類型 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.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
} }
}
return false
}
anyCommonElements([1, 2, 3], [3])
<T: Equatable> <T> ... where T: Equatable>是等價的。