swift——基礎(chǔ)部分

常量和變量

常量和變量把一個(gè)名字(比如maximumNumberOfLoginAttempts或者welcomeMessage)和一個(gè)指定類型的值(比如數(shù)字10或者字符串"Hello")關(guān)聯(lián)起來。常量的值一旦設(shè)定就不能改變,而變量的值可以隨意更改。

聲明常量和變量

常量和變量必須在使用前聲明,用let來聲明常量,用var來聲明變量。下面的例子展示了如何用常量和變量來記錄用戶嘗試登錄的次數(shù):

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

這兩行代碼可以被理解為:

“聲明一個(gè)名字是maximumNumberOfLoginAttempts的新常量,并給它一個(gè)值10。然后,聲明一個(gè)名字是currentLoginAttempt的變量并將它的值初始化為0。”

在這個(gè)例子中,允許的最大嘗試登錄次數(shù)被聲明為一個(gè)常量,因?yàn)檫@個(gè)值不會(huì)改變。當(dāng)前嘗試登錄次數(shù)被聲明為一個(gè)變量,因?yàn)槊看螄L試登錄失敗的時(shí)候都需要增加這個(gè)值。

可以在一行中聲明多個(gè)常量或者多個(gè)變量,用逗號(hào)隔開:

var x = 0.0, y = 0.0, z = 0.0

注意:
如果代碼中有不需要改變的值,請(qǐng)使用let關(guān)鍵字將它聲明為常量。只將需要改變的值聲明為變量。

類型標(biāo)注

當(dāng)聲明常量或者變量的時(shí)候可以加上類型標(biāo)注(type annotation),說明常量或者變量中要存儲(chǔ)的值的類型。如果要添加類型標(biāo)注,需要在常量或者變量名后面加上一個(gè)冒號(hào)和空格,然后加上類型名稱。

這個(gè)例子給welcomeMessage變量添加了類型標(biāo)注,表示這個(gè)變量可以存儲(chǔ)String類型的值:

var welcomeMessage: String

聲明中的冒號(hào)代表著“是...類型”,所以這行代碼可以被理解為:

“聲明一個(gè)類型為String,名字為welcomeMessage的變量?!?/p>

“類型為String”的意思是“可以存儲(chǔ)任意String類型的值?!?/p>

welcomeMessage變量現(xiàn)在可以被設(shè)置成任意字符串:

welcomeMessage = "Hello"

可以在一行中定義多個(gè)同樣類型的變量,用逗號(hào)分割,并在最后一個(gè)變量名之后添加類型標(biāo)注:

var red, green, blue: Double

注意:
一般來說很少需要寫類型標(biāo)注。如果在聲明常量或者變量的時(shí)候賦了一個(gè)初始值,Swift可以推斷出這個(gè)常量或者變量的類型。在上面的例子中,沒有給welcomeMessage賦初始值,所以變量welcomeMessage的類型是通過一個(gè)類型標(biāo)注指定的,而不是通過初始值推斷的。

常量和變量的命名

可以用任何字符作為常量和變量名,包括 Unicode 字符:

let π = 3.14159
let 你好 = "你好世界"
let ???? = "dogcow"

常量與變量名不能包含數(shù)學(xué)符號(hào),箭頭,保留的(或者非法的)Unicode 碼位,連線與制表符。也不能以數(shù)字開頭,但是可以在常量與變量名的其他地方包含數(shù)字。

一旦將常量或者變量聲明為確定的類型,就不能使用相同的名字再次進(jìn)行聲明,或者改變其存儲(chǔ)的值的類型。同時(shí),也不能將常量與變量進(jìn)行互轉(zhuǎn)。

注意:
如果需要使用與Swift保留關(guān)鍵字相同的名稱作為常量或者變量名,可以使用反引號(hào)(`)將關(guān)鍵字包圍的方式將其作為名字使用。無論如何,應(yīng)當(dāng)避免使用關(guān)鍵字作為常量或變量名,除非別無選擇。

可以更改現(xiàn)有的變量值為其他同類型的值,在下面的例子中,friendlyWelcome的值從"Hello!"改為了"Bonjour!":

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 現(xiàn)在是 "Bonjour!"

與變量不同,常量的值一旦被確定就不能更改了。嘗試這樣做會(huì)導(dǎo)致編譯時(shí)報(bào)錯(cuò):

let languageName = "Swift"
languageName = "Swift++"
// 這會(huì)報(bào)編譯時(shí)錯(cuò)誤 - languageName 不可改變

輸出常量和變量

可以用print(_:separator:terminator:)函數(shù)來輸出當(dāng)前常量或變量的值:

print(friendlyWelcome)
// 輸出 "Bonjour!"

print(_:separator:terminator:)是一個(gè)用來輸出一個(gè)或多個(gè)值到適當(dāng)輸出區(qū)的全局函數(shù)。如果用 Xcode,print(_:separator:terminator:)將會(huì)輸出內(nèi)容到“console”面板上。separatorterminator參數(shù)具有默認(rèn)值,因此調(diào)用這個(gè)函數(shù)的時(shí)候可以忽略它們。默認(rèn)情況下,該函數(shù)通過添加換行符來結(jié)束當(dāng)前行。如果不想換行,可以傳遞一個(gè)空字符串給terminator參數(shù)--例如,print(someValue, terminator:"")。

Swift 用字符串插值(string interpolation)的方式把常量名或者變量名當(dāng)做占位符加入到長字符串中,Swift 會(huì)用當(dāng)前常量或變量的值替換這些占位符。將常量或變量名放入圓括號(hào)中,并在開括號(hào)前使用反斜杠將其轉(zhuǎn)義:

print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 輸出 "The current value of friendlyWelcome is Bonjour!

注釋

請(qǐng)將代碼中的非執(zhí)行文本注釋成提示或者筆記以方便將來閱讀。Swift 的編譯器將會(huì)在編譯代碼時(shí)自動(dòng)忽略掉注釋部分。

Swift 中的注釋與 C 語言的注釋非常相似。單行注釋以雙正斜杠(//)作為起始標(biāo)記:

// 這是一個(gè)注釋

也可以進(jìn)行多行注釋,其起始標(biāo)記為單個(gè)正斜杠后跟隨一個(gè)星號(hào)(/*),終止標(biāo)記為一個(gè)星號(hào)后跟隨單個(gè)正斜杠(*/):

/* 這是一個(gè),
多行注釋 */

與 C 語言多行注釋不同,Swift 的多行注釋可以嵌套在其它的多行注釋之中。可以先生成一個(gè)多行注釋塊,然后在這個(gè)注釋塊之中再嵌套成第二個(gè)多行注釋。終止注釋時(shí)先插入第二個(gè)注釋塊的終止標(biāo)記,然后再插入第一個(gè)注釋塊的終止標(biāo)記:

/* 這是第一個(gè)多行注釋的開頭
/* 這是第二個(gè)被嵌套的多行注釋 */
這是第一個(gè)多行注釋的結(jié)尾 */

通過運(yùn)用嵌套多行注釋,可以快速方便的注釋掉一大段代碼,即使這段代碼之中已經(jīng)含有了多行注釋塊。

分號(hào)

與其他大部分編程語言不同,Swift 并不強(qiáng)制要求在每條語句的結(jié)尾處使用分號(hào)(;),當(dāng)然,也可以按照自己的習(xí)慣添加分號(hào)。有一種情況下必須要用分號(hào),即在同一行內(nèi)寫多條獨(dú)立的語句:

let cat = "??"; print(cat)
// 輸出 "??"

整數(shù)

整數(shù)就是沒有小數(shù)部分的數(shù)字,比如42-23。整數(shù)可以是有符號(hào)(正、負(fù)、零)或者無符號(hào)(正、零)。

Swift 提供了8,16,32和64位的有符號(hào)和無符號(hào)整數(shù)類型。這些整數(shù)類型和 C 語言的命名方式很像,比如8位無符號(hào)整數(shù)類型是UInt8,32位有符號(hào)整數(shù)類型是Int32。就像 Swift 的其他類型一樣,整數(shù)類型采用大寫命名法。

整數(shù)范圍

可以訪問不同整數(shù)類型的minmax屬性來獲取對(duì)應(yīng)類型的最小值和最大值:

let minValue = UInt8.min  // minValue 為 0,是 UInt8 類型
let maxValue = UInt8.max  // maxValue 為 255,是 UInt8 類型

minmax所傳回值的類型,正是其所對(duì)的整數(shù)類型(如上例UInt8, 所傳回的類型是UInt8),可用在表達(dá)式中相同類型值旁。

Int

一般來說,不需要專門指定整數(shù)的長度。Swift 提供了一個(gè)特殊的整數(shù)類型Int,長度與當(dāng)前平臺(tái)的原生字長相同:

  • 在32位平臺(tái)上,IntInt32長度相同。
  • 在64位平臺(tái)上,IntInt64長度相同。

除非需要特定長度的整數(shù),一般來說使用Int就夠了。這可以提高代碼一致性和可復(fù)用性。即使是在32位平臺(tái)上,Int可以存儲(chǔ)的整數(shù)范圍也可以達(dá)到-2,147,483,648~2,147,483,647,大多數(shù)時(shí)候這已經(jīng)足夠大了。

UInt

Swift 也提供了一個(gè)特殊的無符號(hào)類型UInt,長度與當(dāng)前平臺(tái)的原生字長相同:

  • 在32位平臺(tái)上,UIntUInt32長度相同。
  • 在64位平臺(tái)上,UIntUInt64長度相同。

注意:
盡量不要使用UInt,除非真的需要存儲(chǔ)一個(gè)和當(dāng)前平臺(tái)原生字長相同的無符號(hào)整數(shù)。除了這種情況,最好使用Int,即使要存儲(chǔ)的值已知是非負(fù)的。統(tǒng)一使用Int可以提高代碼的可復(fù)用性,避免不同類型數(shù)字之間的轉(zhuǎn)換,并且匹配數(shù)字的類型推斷。

浮點(diǎn)數(shù)

浮點(diǎn)數(shù)是有小數(shù)部分的數(shù)字,比如3.14159,0.1-273.15

浮點(diǎn)類型比整數(shù)類型表示的范圍更大,可以存儲(chǔ)比Int類型更大或者更小的數(shù)字。Swift 提供了兩種有符號(hào)浮點(diǎn)數(shù)類型:

  • Double表示64位浮點(diǎn)數(shù)。當(dāng)需要存儲(chǔ)很大或者很高精度的浮點(diǎn)數(shù)時(shí)請(qǐng)使用此類型。
  • Float表示32位浮點(diǎn)數(shù)。精度要求不高的話可以使用此類型。

注意:
Double精確度很高,至少有15位數(shù)字,而Float最少只有6位數(shù)字。選擇哪個(gè)類型取決于代碼需要處理的值的范圍。

類型安全和類型推斷

Swift 是一個(gè)類型安全(type safe)的語言。類型安全的語言可以清楚地知道代碼要處理的值的類型。如果代碼需要一個(gè)String,絕對(duì)不可能不小心傳進(jìn)去一個(gè)Int。

由于 Swift 是類型安全的,所以它會(huì)在編譯代碼時(shí)進(jìn)行類型檢查(type checks),并把不匹配的類型標(biāo)記為錯(cuò)誤。這可以在開發(fā)的時(shí)候盡早發(fā)現(xiàn)并修復(fù)錯(cuò)誤。

當(dāng)要處理不同類型的值時(shí),類型檢查可以避免錯(cuò)誤。然而,這并不是說每次聲明常量和變量的時(shí)候都需要顯式指定類型。如果沒有顯式指定類型,Swift 會(huì)使用類型推斷(type inference)來選擇合適的類型。有了類型推斷,編譯器可以在編譯代碼的時(shí)候自動(dòng)推斷出表達(dá)式的類型。原理很簡單,只要檢查賦的值即可。

因?yàn)橛蓄愋屯茢啵?C 或者 Objective-C 比起來 Swift 很少需要聲明類型。常量和變量雖然需要明確類型,但是大部分工作并不需要程序員來完成。

當(dāng)聲明常量或者變量并賦初值的時(shí)候類型推斷非常有用。在聲明常量或者變量的時(shí)候賦給它們一個(gè)字面量(literal value 或 literal)即可觸發(fā)類型推斷。(字面量就是會(huì)直接出現(xiàn)在代碼中的值,比如423.14159。)

例如,如果給一個(gè)新常量賦值42并且沒有標(biāo)明類型,Swift 可以推斷出常量類型是Int,因?yàn)榻o它賦的初始值看起來像一個(gè)整數(shù):

let meaningOfLife = 42
// meaningOfLife 會(huì)被推測為 Int 類型

同理,如果沒有給浮點(diǎn)字面量標(biāo)明類型,Swift 會(huì)推斷想要的是Double

let pi = 3.14159
// pi 會(huì)被推測為 Double 類型

注意:
當(dāng)推斷浮點(diǎn)數(shù)的類型時(shí),Swift 總是會(huì)選擇Double而不是Float

如果表達(dá)式中同時(shí)出現(xiàn)了整數(shù)和浮點(diǎn)數(shù),會(huì)被推斷為Double類型:

let anotherPi = 3 + 0.14159
// anotherPi 會(huì)被推測為 Double 類型

原始值3沒有顯式聲明類型,而表達(dá)式中出現(xiàn)了一個(gè)浮點(diǎn)字面量,所以表達(dá)式會(huì)被推斷為Double類型。

數(shù)值型字面量

整數(shù)字面量可以被寫作:

  • 一個(gè)十進(jìn)制數(shù),沒有前綴
  • 一個(gè)二進(jìn)制數(shù),前綴是0b
  • 一個(gè)八進(jìn)制數(shù),前綴是0o
  • 一個(gè)十六進(jìn)制數(shù),前綴是0x

下面的所有整數(shù)字面量的十進(jìn)制值都是17:

let decimalInteger = 17
let binaryInteger = 0b10001       // 二進(jìn)制的17
let octalInteger = 0o21           // 八進(jìn)制的17
let hexadecimalInteger = 0x11     // 十六進(jìn)制的17

浮點(diǎn)字面量可以是十進(jìn)制(沒有前綴)或者是十六進(jìn)制(前綴是0x)。小數(shù)點(diǎn)兩邊必須有至少一個(gè)十進(jìn)制數(shù)字(或者是十六進(jìn)制的數(shù)字)。浮點(diǎn)字面量還有一個(gè)可選的指數(shù)(exponent,在十進(jìn)制浮點(diǎn)數(shù)中通過大寫或者小寫的e來指定,在十六進(jìn)制浮點(diǎn)數(shù)中通過大寫或者小寫的p來指定。

如果一個(gè)十進(jìn)制數(shù)的指數(shù)為exp,那這個(gè)數(shù)相當(dāng)于基數(shù)和10^exp的乘積:

  • 1.25e2 表示 1.25 × 10^2,等于 125.0
  • 1.25e-2 表示 1.25 × 10^-2,等于 0.0125。

如果一個(gè)十六進(jìn)制數(shù)的指數(shù)為exp,那這個(gè)數(shù)相當(dāng)于基數(shù)和2^exp的乘積:

  • 0xFp2 表示 15 × 2^2,等于 60.0。
  • 0xFp-2 表示 15 × 2^-2,等于 3.75。

下面的這些浮點(diǎn)字面量都等于十進(jìn)制的12.1875

let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

數(shù)值類字面量可以包括額外的格式來增強(qiáng)可讀性。整數(shù)和浮點(diǎn)數(shù)都可以添加額外的零并且包含下劃線,并不會(huì)影響字面量:

let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

數(shù)值型類型轉(zhuǎn)換

通常來講,即使代碼中的整數(shù)常量和變量已知非負(fù),也請(qǐng)使用Int類型??偸鞘褂媚J(rèn)的整數(shù)類型可以保證整數(shù)常量和變量可以直接被復(fù)用并且可以匹配整數(shù)類字面量的類型推斷。

只有在必要的時(shí)候才使用其他整數(shù)類型,比如要處理外部的長度明確的數(shù)據(jù)或者為了優(yōu)化性能、內(nèi)存占用等等。使用顯式指定長度的類型可以及時(shí)發(fā)現(xiàn)值溢出并且可以暗示正在處理特殊數(shù)據(jù)。

整數(shù)轉(zhuǎn)換

不同整數(shù)類型的變量和常量可以存儲(chǔ)不同范圍的數(shù)字。Int8類型的常量或者變量可以存儲(chǔ)的數(shù)字范圍是-128`127`,而`UInt8`類型的常量或者變量能存儲(chǔ)的數(shù)字范圍是`0`255。如果數(shù)字超出了常量或者變量可存儲(chǔ)的范圍,編譯的時(shí)候會(huì)報(bào)錯(cuò):

let cannotBeNegative: UInt8 = -1
// UInt8 類型不能存儲(chǔ)負(fù)數(shù),所以會(huì)報(bào)錯(cuò)
let tooBig: Int8 = Int8.max + 1
// Int8 類型不能存儲(chǔ)超過最大值的數(shù),所以會(huì)報(bào)錯(cuò)

由于每種整數(shù)類型都可以存儲(chǔ)不同范圍的值,所以必須根據(jù)不同情況選擇性使用數(shù)值型類型轉(zhuǎn)換。這種選擇性使用的方式,可以預(yù)防隱式轉(zhuǎn)換的錯(cuò)誤并讓代碼中的類型轉(zhuǎn)換意圖變得清晰。

要將一種數(shù)字類型轉(zhuǎn)換成另一種,要用當(dāng)前值來初始化一個(gè)期望類型的新數(shù)字,這個(gè)數(shù)字的類型就是目標(biāo)類型。在下面的例子中,常量twoThousandUInt16類型,然而常量oneUInt8類型。它們不能直接相加,因?yàn)樗鼈冾愋筒煌?。所以要調(diào)用UInt16(one)來創(chuàng)建一個(gè)新的UInt16數(shù)字并用one的值來初始化,然后使用這個(gè)新數(shù)字來計(jì)算:

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

現(xiàn)在兩個(gè)數(shù)字的類型都是UInt16,可以進(jìn)行相加。目標(biāo)常量twoThousandAndOne的類型被推斷為UInt16,因?yàn)樗莾蓚€(gè)UInt16值的和。

SomeType(ofInitialValue)是調(diào)用 Swift 構(gòu)造器并傳入一個(gè)初始值的默認(rèn)方法。在語言內(nèi)部,UInt16有一個(gè)構(gòu)造器,可以接受一個(gè)UInt8類型的值,所以這個(gè)構(gòu)造器可以用現(xiàn)有的UInt8來創(chuàng)建一個(gè)新的UInt16。注意,并不能傳入任意類型的值,只能傳入UInt16內(nèi)部有對(duì)應(yīng)構(gòu)造器的值。不過可以擴(kuò)展現(xiàn)有的類型來讓它可以接收其他類型的值(包括自定義類型)。

整數(shù)和浮點(diǎn)數(shù)轉(zhuǎn)換

整數(shù)和浮點(diǎn)數(shù)的轉(zhuǎn)換必須顯式指定類型:

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推測為 Double 類型

這個(gè)例子中,常量three的值被用來創(chuàng)建一個(gè)Double類型的值,所以加號(hào)兩邊的數(shù)類型須相同。如果不進(jìn)行轉(zhuǎn)換,兩者無法相加。

浮點(diǎn)數(shù)到整數(shù)的反向轉(zhuǎn)換同樣行,整數(shù)類型可以用Double或者Float類型來初始化:

let integerPi = Int(pi)
// integerPi 等于 3,所以被推測為 Int 類型

當(dāng)用這種方式來初始化一個(gè)新的整數(shù)值時(shí),浮點(diǎn)值會(huì)被截?cái)?。也就是說4.75會(huì)變成4-3.9會(huì)變成-3。

注意:
結(jié)合數(shù)字類常量和變量不同于結(jié)合數(shù)字類字面量。字面量3可以直接和字面量0.14159相加,因?yàn)閿?shù)字字面量本身沒有明確的類型。它們的類型只在編譯器需要求值的時(shí)候被推測。

類型別名

類型別名(type aliases)就是給現(xiàn)有類型定義另一個(gè)名字??梢允褂?code>typealias關(guān)鍵字來定義類型別名。

想要給現(xiàn)有類型起一個(gè)更有意義的名字時(shí),類型別名非常有用。假設(shè)正在處理特定長度的外部資源的數(shù)據(jù):

typealias AudioSample = UInt16

定義了一個(gè)類型別名之后,可以在任何使用原始名的地方使用別名:

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 現(xiàn)在是 0

本例中,AudioSample被定義為UInt16的一個(gè)別名。因?yàn)樗莿e名,AudioSample.min實(shí)際上是UInt16.min,所以會(huì)給maxAmplitudeFound賦一個(gè)初值0

布爾值

Swift 有一個(gè)基本的布爾(Boolean)類型,叫做Bool。布爾值指邏輯上的值,因?yàn)樗鼈冎荒苁钦婊蛘呒?。Swift 有兩個(gè)布爾常量,truefalse

let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrangeturnipsAreDelicious的類型會(huì)被推斷為Bool,因?yàn)樗鼈兊某踔凳遣紶栕置媪?。就像之前提到?code>Int和Double一樣,如果創(chuàng)建變量的時(shí)候給它們賦值true或者false,那么不需要將常量或者變量聲明為Bool類型。初始化常量或者變量的時(shí)候如果所賦的值類型已知,就可以觸發(fā)類型推斷,這讓 Swift 代碼更加簡潔并且可讀性更高。

當(dāng)編寫條件語句比如if語句的時(shí)候,布爾值非常有用:

if turnipsAreDelicious {
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}
// 輸出 "Eww, turnips are horrible."

如果在需要使用Bool類型的地方使用了非布爾值,Swift 的類型安全機(jī)制會(huì)報(bào)錯(cuò)。下面的例子會(huì)報(bào)告一個(gè)編譯時(shí)錯(cuò)誤:

let i = 1
if i {
    // 這個(gè)例子不會(huì)通過編譯,會(huì)報(bào)錯(cuò)
}

然而,下面的例子是合法的:

let i = 1
if i == 1 {
    // 這個(gè)例子會(huì)編譯成功
}

i == 1的比較結(jié)果是Bool類型,所以第二個(gè)例子可以通過類型檢查。

和 Swift 中的其他類型安全的例子一樣,這個(gè)方法可以避免錯(cuò)誤并保證這塊代碼的意圖總是清晰的。

元組

元組(tuples)把多個(gè)值組合成一個(gè)復(fù)合值。元組內(nèi)的值可以是任意類型,并不要求是相同類型。

下面這個(gè)例子中,(404, "Not Found")是一個(gè)描述 HTTP 狀態(tài)碼(HTTP status code)的元組。HTTP 狀態(tài)碼是當(dāng)請(qǐng)求網(wǎng)頁的時(shí)候 web 服務(wù)器返回的一個(gè)特殊值。如果請(qǐng)求的網(wǎng)頁不存在就會(huì)返回一個(gè)404 Not Found狀態(tài)碼。

let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")

(404, "Not Found")元組把一個(gè)Int值和一個(gè)String值組合起來表示 HTTP 狀態(tài)碼的兩個(gè)部分:一個(gè)數(shù)字和一個(gè)人類可讀的描述。這個(gè)元組可以被描述為“一個(gè)類型為(Int, String)的元組”。

可以把任意順序的類型組合成一個(gè)元組,這個(gè)元組可以包含所有類型。只要你想,你可以創(chuàng)建一個(gè)類型為(Int, Int, Int)或者(String, Bool)或者其他任何你想要的組合的元組。

可以將一個(gè)元組的內(nèi)容分解(decompose)成單獨(dú)的常量和變量,然后就可以正常使用它們了:

let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 輸出 "The status code is 404"
print("The status message is \(statusMessage)")
// 輸出 "The status message is Not Found"

如果只需要一部分元組值,分解的時(shí)候可以把要忽略的部分用下劃線(_)標(biāo)記:

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 輸出 "The status code is 404"

此外,還可以通過下標(biāo)來訪問元組中的單個(gè)元素,下標(biāo)從零開始:

print("The status code is \(http404Error.0)")
// 輸出 "The status code is 404"
print("The status message is \(http404Error.1)")
// 輸出 "The status message is Not Found"

可以在定義元組的時(shí)候給單個(gè)元素命名:

let http200Status = (statusCode: 200, description: "OK")

給元組中的元素命名后,可以通過名字來獲取這些元素的值:

print("The status code is \(http200Status.statusCode)")
// 輸出 "The status code is 200"
print("The status message is \(http200Status.description)")
// 輸出 "The status message is OK"

作為函數(shù)返回值時(shí),元組非常有用。一個(gè)用來獲取網(wǎng)頁的函數(shù)可能會(huì)返回一個(gè)(Int, String)元組來描述是否獲取成功。和只能返回一個(gè)類型的值比較起來,一個(gè)包含兩個(gè)不同類型值的元組可以讓函數(shù)的返回信息更有用。

注意:
元組在臨時(shí)組織值的時(shí)候很有用,但是并不適合創(chuàng)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。如果數(shù)據(jù)結(jié)構(gòu)并不是臨時(shí)使用,請(qǐng)使用類或者結(jié)構(gòu)體而不是元組。

可選類型

使用可選類型(optionals)來處理值可能缺失的情況。可選類型表示:

  • 有值,等于 x

或者

  • 沒有值

注意:
C 和 Objective-C 中并沒有可選類型這個(gè)概念。最接近的是 Objective-C 中的一個(gè)特性,一個(gè)方法要不返回一個(gè)對(duì)象要不返回nilnil表示“缺少一個(gè)合法的對(duì)象”。然而,這只對(duì)對(duì)象起作用——對(duì)于結(jié)構(gòu)體,基本的 C 類型或者枚舉類型不起作用。對(duì)于這些類型,Objective-C 方法一般會(huì)返回一個(gè)特殊值(比如NSNotFound)來暗示值缺失。這種方法假設(shè)方法的調(diào)用者知道并記得對(duì)特殊值進(jìn)行判斷。然而,Swift 的可選類型可以暗示任意類型的值缺失,并不需要一個(gè)特殊值。

來看一個(gè)例子。Swift 的String類型有一種構(gòu)造器,作用是將一個(gè)String值轉(zhuǎn)換成一個(gè)Int值。然而,并不是所有的字符串都可以轉(zhuǎn)換成一個(gè)整數(shù)。字符串"123"可以被轉(zhuǎn)換成數(shù)字123,但是字符串"hello, world"不行。

下面的例子使用這種構(gòu)造器來嘗試將一個(gè)String轉(zhuǎn)換成Int

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測為類型 "Int?", 或者類型 "optional Int"

因?yàn)樵摌?gòu)造器可能會(huì)失敗,所以它返回一個(gè)可選類型(optional)Int,而不是一個(gè)Int。一個(gè)可選的Int被寫作Int?而不是Int。問號(hào)暗示包含的值是可選類型,也就是說可能包含Int值也可能不包含值。(不能包含其他任何值比如Bool值或者String值。只能是Int或者什么都沒有。)

nil

可以給可選變量賦值為nil來表示它沒有值:

var serverResponseCode: Int? = 404
// serverResponseCode 包含一個(gè)可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值

注意:
nil不能用于非可選的常量和變量。如果代碼中有常量或者變量需要處理值缺失的情況,請(qǐng)把它們聲明成對(duì)應(yīng)的可選類型。

如果聲明一個(gè)可選常量或者變量但是沒有賦值,它們會(huì)自動(dòng)被設(shè)置為nil

var surveyAnswer: String?
// surveyAnswer 被自動(dòng)設(shè)置為 nil

注意:
Swift 的nil和 Objective-C 中的nil并不一樣。在 Objective-C 中,nil是一個(gè)指向不存在對(duì)象的指針。在 Swift 中,nil不是指針——它是一個(gè)確定的值,用來表示值缺失。任何類型的可選狀態(tài)都可以被設(shè)置為nil,不只是對(duì)象類型。

if 語句以及強(qiáng)制解析

可以使用if語句和nil比較來判斷一個(gè)可選值是否包含值??梢允褂谩跋嗟取?==)或“不等”(!=)來執(zhí)行比較。

如果可選類型有值,它將不等于nil:

if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// 輸出 "convertedNumber contains some integer value."

當(dāng)確定可選類型確實(shí)包含值之后,可以在可選的名字后面加一個(gè)感嘆號(hào)(!)來獲取值。這個(gè)驚嘆號(hào)表示“我知道這個(gè)可選有值,請(qǐng)使用它?!边@被稱為可選值的強(qiáng)制解析(forced unwrapping)

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 輸出 "convertedNumber has an integer value of 123."

注意:
使用!來獲取一個(gè)不存在的可選值會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。使用!來強(qiáng)制解析值之前,一定要確定可選包含一個(gè)非nil的值。

可選綁定

使用可選綁定(optional binding)來判斷可選類型是否包含值,如果包含就把值賦給一個(gè)臨時(shí)常量或者變量??蛇x綁定可以用在ifwhile語句中,這條語句不僅可以用來判斷可選類型中是否有值,同時(shí)可以將可選類型中的值賦給一個(gè)常量或者變量。

像下面這樣在if語句中寫一個(gè)可選綁定:

if let constantName = someOptional {
    statements
}

可以像上面這樣使用可選綁定來重寫possibleNumber這個(gè)例子:

if let actualNumber = Int(possibleNumber) {
    print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 輸出 "'123' has an integer value of 123"

這段代碼可以被理解為:

“如果Int(possibleNumber)返回的可選Int包含一個(gè)值,創(chuàng)建一個(gè)叫做actualNumber的新常量并將可選包含的值賦給它?!?/p>

如果轉(zhuǎn)換成功,actualNumber常量可以在if語句的第一個(gè)分支中使用。它已經(jīng)被可選類型包含的值初始化過,所以不需要再使用!后綴來獲取它的值。在這個(gè)例子中,actualNumber只被用來輸出轉(zhuǎn)換結(jié)果。

可以在可選綁定中使用常量和變量。如果想在if語句的第一個(gè)分支中操作actualNumber的值,可以改成if var actualNumber,這樣可選類型包含的值就會(huì)被賦給一個(gè)變量而非常量。

可以包含多個(gè)可選綁定在if語句中,并使用where子句做布爾值判斷。

if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber {
    print("\(firstNumber) < \(secondNumber)")
}
// prints "4 < 42"

隱式解析可選類型

如上所述,可選類型暗示了常量或者變量可以“沒有值”??蛇x可以通過if語句來判斷是否有值,如果有值的話可以通過可選綁定來解析值。

有時(shí)候在程序架構(gòu)中,第一次被賦值之后,可以確定一個(gè)可選類型總會(huì)有值。在這種情況下,每次都要判斷和解析可選值是非常低效的,因?yàn)榭梢源_定它總會(huì)有值。

這種類型的可選狀態(tài)被定義為隱式解析可選類型(implicitly unwrapped optionals)。把想要用作可選的類型的后面的問號(hào)(String?)改成感嘆號(hào)(String!)來聲明一個(gè)隱式解析可選類型。

當(dāng)可選類型被第一次賦值之后就可以確定之后一直有值的時(shí)候,隱式解析可選類型非常有用。隱式解析可選類型主要被用在 Swift 中類的構(gòu)造過程中。

一個(gè)隱式解析可選類型其實(shí)就是一個(gè)普通的可選類型,但是可以被當(dāng)做非可選類型來使用,并不需要每次都使用解析來獲取可選值。下面的例子展示了可選類型String和隱式解析可選類型String之間的區(qū)別:

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要驚嘆號(hào)來獲取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString  // 不需要感嘆號(hào)

可以把隱式解析可選類型當(dāng)做一個(gè)可以自動(dòng)解析的可選類型。要做的只是聲明的時(shí)候把感嘆號(hào)放到類型的結(jié)尾,而不是每次取值的可選名字的結(jié)尾。

注意:
如果在隱式解析可選類型沒有值的時(shí)候嘗試取值,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。和在沒有值的普通可選類型后面加一個(gè)驚嘆號(hào)一樣。

仍然可以把隱式解析可選類型當(dāng)做普通可選類型來判斷它是否包含值:

if assumedString != nil {
    print(assumedString)
}
// 輸出 "An implicitly unwrapped optional string."

也可以在可選綁定中使用隱式解析可選類型來檢查并解析它的值:

if let definiteString = assumedString {
    print(definiteString)
}
// 輸出 "An implicitly unwrapped optional string."

注意:
如果一個(gè)變量之后可能變成nil的話請(qǐng)不要使用隱式解析可選類型。如果需要在變量的生命周期中判斷是否是nil的話,請(qǐng)使用普通可選類型。

錯(cuò)誤處理

可以使用錯(cuò)誤處理(error handling)來應(yīng)對(duì)程序執(zhí)行中可能會(huì)遇到的錯(cuò)誤條件。

相對(duì)于可選中運(yùn)用值的存在與缺失來表達(dá)函數(shù)的成功與失敗,錯(cuò)誤處理可以推斷失敗的原因,并傳播至程序的其他部分。

當(dāng)一個(gè)函數(shù)遇到錯(cuò)誤條件,它能報(bào)錯(cuò)。調(diào)用函數(shù)的地方能拋出錯(cuò)誤消息并合理處理。

func canThrowAnError() throws {
    // 這個(gè)函數(shù)有可能拋出錯(cuò)誤
}

一個(gè)函數(shù)可以通過在聲明中添加throws關(guān)鍵詞來拋出錯(cuò)誤消息。當(dāng)函數(shù)能拋出錯(cuò)誤消息時(shí), 應(yīng)該在表達(dá)式中前置try關(guān)鍵詞。

do {
    try canThrowAnError()
    // 沒有錯(cuò)誤消息拋出
} catch {
    // 有一個(gè)錯(cuò)誤消息拋出
}

一個(gè)do語句創(chuàng)建了一個(gè)新的包含作用域,使得錯(cuò)誤能被傳播到一個(gè)或多個(gè)catch從句。

這里有一個(gè)錯(cuò)誤處理如何用來應(yīng)對(duì)不同錯(cuò)誤條件的例子。

func makeASandwich() throws {
    // ...
}

do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在此例中,makeASandwich()(做一個(gè)三明治)函數(shù)會(huì)拋出一個(gè)錯(cuò)誤消息如果沒有干凈的盤子或者某個(gè)原料缺失。因?yàn)?code>makeASandwich()拋出錯(cuò)誤,函數(shù)調(diào)用被包裹在try表達(dá)式中。將函數(shù)包裹在一個(gè)do語句中,任何被拋出的錯(cuò)誤會(huì)被傳播到提供的catch從句中。

如果沒有錯(cuò)誤被拋出, eatASandwich()函數(shù)會(huì)被調(diào)用。如果一個(gè)匹配Error.OutOfCleanDishes的錯(cuò)誤被拋出,washDishes函數(shù)會(huì)被調(diào)用。如果一個(gè)匹配Error.MissingIngredients的錯(cuò)誤被拋出,buyGroceries(_:)函數(shù)會(huì)隨著被catch所捕捉到的關(guān)聯(lián)值[String]被調(diào)用。

斷言

可選類型可以判斷值是否存在,可以在代碼中優(yōu)雅地處理值缺失的情況。然而,在某些情況下,如果值缺失或者值并不滿足特定的條件,代碼可能沒辦法繼續(xù)執(zhí)行。這時(shí),可以在代碼中觸發(fā)一個(gè)斷言(assertion)來結(jié)束代碼運(yùn)行并通過調(diào)試來找到值缺失的原因。

使用斷言進(jìn)行調(diào)試

斷言會(huì)在運(yùn)行時(shí)判斷一個(gè)邏輯條件是否為true。從字面意思來說,斷言“斷言”一個(gè)條件是否為真??梢允褂脭嘌詠肀WC在運(yùn)行其他代碼之前,某些重要的條件已經(jīng)被滿足。如果條件判斷為true,代碼運(yùn)行會(huì)繼續(xù)進(jìn)行;如果條件判斷為false,代碼執(zhí)行結(jié)束,應(yīng)用被終止。

如果代碼在調(diào)試環(huán)境下觸發(fā)了一個(gè)斷言,比如在 Xcode 中構(gòu)建并運(yùn)行一個(gè)應(yīng)用,可以清楚地看到不合法的狀態(tài)發(fā)生在哪里并檢查斷言被觸發(fā)時(shí)應(yīng)用的狀態(tài)。此外,斷言允許附加一條調(diào)試信息。

可以使用全局assert(_:_file:line:)函數(shù)來寫一個(gè)斷言。向這個(gè)函數(shù)傳入一個(gè)結(jié)果為true或者false的表達(dá)式以及一條信息,當(dāng)表達(dá)式的結(jié)果為false的時(shí)候這條信息會(huì)被顯示:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因?yàn)?age < 0,所以斷言會(huì)觸發(fā)

在這個(gè)例子中,只有age >= 0true的時(shí)候,即age的值非負(fù)的時(shí)候,代碼才會(huì)繼續(xù)執(zhí)行。如果age的值是負(fù)數(shù),就像代碼中那樣,age >= 0false,斷言被觸發(fā),終止應(yīng)用。

如果不需要斷言信息,可以省略,就像這樣:

assert(age >= 0)

注意:
當(dāng)代碼使用優(yōu)化編譯的時(shí)候,斷言將會(huì)被禁用,例如在 Xcode 中,使用默認(rèn)的 target Release 配置選項(xiàng)來 build 時(shí),斷言會(huì)被禁用。

何時(shí)使用斷言

當(dāng)條件可能為假時(shí)使用斷言,但是最終一定要保證條件為真,這樣代碼才能繼續(xù)運(yùn)行。斷言的適用情景:

  • 整數(shù)類型的下標(biāo)索引被傳入一個(gè)自定義下標(biāo)腳本實(shí)現(xiàn),但是下標(biāo)索引值可能太小或者太大。
  • 需要給函數(shù)傳入一個(gè)值,但是非法的值可能導(dǎo)致函數(shù)不能正常執(zhí)行。
  • 一個(gè)可選值現(xiàn)在是nil,但是后面的代碼運(yùn)行需要一個(gè)非nil值。

注意:
斷言可能導(dǎo)致應(yīng)用終止運(yùn)行,所以應(yīng)當(dāng)仔細(xì)設(shè)計(jì)代碼來讓非法條件不會(huì)出現(xiàn)。然而,在應(yīng)用發(fā)布之前,有時(shí)候非法條件可能出現(xiàn),這時(shí)使用斷言可以快速發(fā)現(xiàn)問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容