Swift 是一門開發(fā) iOS, macOS, watchOS 和 tvOS 應(yīng)用的新語言蹄梢。然而傍睹,如果你有 C 或者 Objective-C 開發(fā)經(jīng)驗的話搀菩,你會發(fā)現(xiàn) Swift 的很多內(nèi)容都是你熟悉的螃成。
Swift 包含了 C 和 Objective-C 上所有基礎(chǔ)數(shù)據(jù)類型旦签,Int表示整型值; Double 和 Float 表示浮點型值寸宏; Bool 是布爾型值宁炫;String 是文本型數(shù)據(jù)。 Swift 還提供了三個基本的集合類型氮凝,Array 淋淀,Set 和 Dictionary ,詳見集合類型覆醇。
就像 C 語言一樣朵纷,Swift 使用變量來進行存儲并通過變量名來關(guān)聯(lián)值。在 Swift 中永脓,廣泛的使用著值不可變的變量袍辞,它們就是常量,而且比 C 語言的常量更強大常摧。在 Swift 中搅吁,如果你要處理的值不需要改變,那使用常量可以讓你的代碼更加安全并且更清晰地表達你的意圖落午。
除了我們熟悉的類型谎懦,Swift 還增加了 Objective-C 中沒有的高階數(shù)據(jù)類型比如元組(Tuple)。元組可以讓你創(chuàng)建或者傳遞一組數(shù)據(jù)溃斋,比如作為函數(shù)的返回值時界拦,你可以用一個元組可以返回多個值。
Swift 還增加了可選(Optional)類型梗劫,用于處理值缺失的情況享甸。可選表示 “那兒有一個值梳侨,并且它等于 x ” 或者 “那兒沒有值” 蛉威。可選有點像在 Objective-C 中使用 nil 走哺,但是它可以用在任何類型上蚯嫌,不僅僅是類。可選類型比 Objective-C 中的 nil 指針更加安全也更具表現(xiàn)力择示,它是 Swift 許多強大特性的重要組成部分束凑。
Swift 是一門類型安全的語言,這意味著 Swift 可以讓你清楚地知道值的類型对妄。如果你的代碼需要一個 String 湘今,類型安全會阻止你不小心傳入一個 Int 。同樣的剪菱,如果你的代碼需要一個 String摩瞎,類型安全會阻止你意外傳入一個可選的 String 。類型安全可以幫助你在開發(fā)階段盡早發(fā)現(xiàn)并修正錯誤孝常。
常量和變量
常量和變量把一個名字(比如 maximumNumberOfLoginAttempts 或者 welcomeMessage )和一個指定類型的值(比如數(shù)字 10 或者字符串 "Hello" )關(guān)聯(lián)起來旗们。常量的值一旦設(shè)定就不能改變,而變量的值可以隨意更改构灸。
聲明常量和變量
常量和變量必須在使用前聲明上渴,用 let 來聲明常量,用 var 來聲明變量喜颁。下面的例子展示了如何用常量和變量來記錄用戶嘗試登錄的次數(shù):
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
這兩行代碼可以被理解為:
“聲明一個名字是 maximumNumberOfLoginAttempts 的新常量稠氮,并給它一個值 10 。然后半开,聲明一個名字是 currentLoginAttempt 的變量并將它的值初始化為 0 隔披。”
在這個例子中寂拆,允許的最大嘗試登錄次數(shù)被聲明為一個常量奢米,因為這個值不會改變。當(dāng)前嘗試登錄次數(shù)被聲明為一個變量纠永,因為每次嘗試登錄失敗的時候都需要增加這個值鬓长。
你可以在一行中聲明多個常量或者多個變量,用逗號隔開:
var x = 0.0, y = 0.0, z = 0.0
注意:
如果你的代碼中有不需要改變的值尝江,請使用 let 關(guān)鍵字將它聲明為常量涉波。只將需要改變的值聲明為變量。
類型標(biāo)注
當(dāng)你聲明常量或者變量的時候可以加上類型標(biāo)注(type annotation)茂装,說明常量或者變量中要存儲的值的類型怠蹂。如果要添加類型標(biāo)注,需要在常量或者變量名后面加上一個冒號和空格少态,然后加上類型名稱。
這個例子給 welcomeMessage 變量添加了類型標(biāo)注易遣,表示這個變量可以存儲 String 類型的值:
var welcomeMessage: String
聲明中的冒號代表著“是...類型”彼妻,所以這行代碼可以被理解為:
“聲明一個類型為 String ,名字為 welcomeMessage 的變量∏惹福”
“類型為 String ”的意思是“可以存儲任意 String 類型的值屋摇。”
welcomeMessage 變量現(xiàn)在可以被設(shè)置成任意字符串:
welcomeMessage = "Hello"
你可以在一行中定義多個同樣類型的變量幽邓,用逗號分割炮温,并在最后一個變量名之后添加類型標(biāo)注:
var red, green, blue: Double
注意:
一般來說你很少需要寫類型標(biāo)注。如果你在聲明常量或者變量的時候賦了一個初始值牵舵,Swift可以推斷出這個常量或者變量的類型柒啤,請參考類型安全和類型推斷。在上面的例子中畸颅,沒有給 welcomeMessage 賦初始值担巩,所以變量 welcomeMessage 的類型是通過一個類型標(biāo)注指定的,而不是通過初始值推斷的没炒。
常量和變量的命名
你可以用任何你喜歡的字符作為常量和變量名涛癌,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let ???? = "dogcow"
常量與變量名不能包含數(shù)學(xué)符號,箭頭送火,保留的(或者非法的)Unicode 碼位拳话,連線與制表符。也不能以數(shù)字開頭种吸,但是可以在常量與變量名的其他地方包含數(shù)字弃衍。
一旦你將常量或者變量聲明為確定的類型,你就不能使用相同的名字再次進行聲明骨稿,或者改變其存儲的值的類型笨鸡。同時,你也不能將常量與變量進行互轉(zhuǎn)坦冠。
注意:
如果你需要使用與Swift保留關(guān)鍵字相同的名稱作為常量或者變量名形耗,你可以使用反引號(`)將關(guān)鍵字包圍的方式將其作為名字使用。無論如何辙浑,你應(yīng)當(dāng)避免使用關(guān)鍵字作為常量或變量名激涤,除非你別無選擇。
你可以更改現(xiàn)有的變量值為其他同類型的值判呕,在下面的例子中倦踢,friendlyWelcome的值從"Hello!"改為了"Bonjour!":
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 現(xiàn)在是 "Bonjour!"
與變量不同,常量的值一旦被確定就不能更改了侠草。嘗試這樣做會導(dǎo)致編譯時報錯:
let languageName = "Swift"
languageName = "Swift++"
// 這會報編譯時錯誤 - languageName 不可改變
輸出常量和變量
你可以用print(_:separator:terminator:)函數(shù)來輸出當(dāng)前常量或變量的值:
print(friendlyWelcome)
// 輸出 "Bonjour!"
print(:separator:terminator:) 是一個用來輸出一個或多個值到適當(dāng)輸出區(qū)的全局函數(shù)辱挥。如果你用 Xcode,print(:separator:terminator:) 將會輸出內(nèi)容到“console”面板上边涕。separator 和 terminator 參數(shù)具有默認(rèn)值晤碘,因此你調(diào)用這個函數(shù)的時候可以忽略它們褂微。默認(rèn)情況下,該函數(shù)通過添加換行符來結(jié)束當(dāng)前行园爷。如果不想換行宠蚂,可以傳遞一個空字符串給 terminator 參數(shù)--例如,print(someValue, terminator:"") 童社。關(guān)于參數(shù)默認(rèn)值的更多信息求厕,請參考默認(rèn)參數(shù)值。
Swift 用字符串插值(string interpolation)的方式把常量名或者變量名當(dāng)做占位符加入到長字符串中扰楼,Swift 會用當(dāng)前常量或變量的值替換這些占位符呀癣。將常量或變量名放入圓括號中,并在開括號前使用反斜杠將其轉(zhuǎn)義:
print("The current value of friendlyWelcome is (friendlyWelcome)")
// 輸出 "The current value of friendlyWelcome is Bonjour!
注意:
字符串插值所有可用的選項灭抑,請參考字符串插值十艾。
注釋
請將你的代碼中的非執(zhí)行文本注釋成提示或者筆記以方便你將來閱讀。Swift 的編譯器將會在編譯代碼時自動忽略掉注釋部分腾节。
Swift 中的注釋與 C 語言的注釋非常相似忘嫉。單行注釋以雙正斜杠(//)作為起始標(biāo)記:
// 這是一個注釋
你也可以進行多行注釋,其起始標(biāo)記為單個正斜杠后跟隨一個星號(/)案腺,終止標(biāo)記為一個星號后跟隨單個正斜杠(/):
/* 這是一個, 多行注釋 */
與 C 語言多行注釋不同庆冕,Swift 的多行注釋可以嵌套在其它的多行注釋之中。你可以先生成一個多行注釋塊劈榨,然后在這個注釋塊之中再嵌套成第二個多行注釋访递。終止注釋時先插入第二個注釋塊的終止標(biāo)記,然后再插入第一個注釋塊的終止標(biāo)記:
/* 這是第一個多行注釋的開頭 /* 這是第二個被嵌套的多行注釋 */ 這是第一個多行注釋的結(jié)尾 */
通過運用嵌套多行注釋同辣,你可以快速方便的注釋掉一大段代碼拷姿,即使這段代碼之中已經(jīng)含有了多行注釋塊。
分號
與其他大部分編程語言不同旱函,Swift 并不強制要求你在每條語句的結(jié)尾處使用分號(;)响巢,當(dāng)然,你也可以按照你自己的習(xí)慣添加分號棒妨。有一種情況下必須要用分號踪古,即你打算在同一行內(nèi)寫多條獨立的語句:
let cat = "??"; print(cat)
// 輸出 "??"
整數(shù)
整數(shù)就是沒有小數(shù)部分的數(shù)字,比如 42 和 -23 券腔。整數(shù)可以是 有符號(正伏穆、負(fù)、零)或者 無符號(正纷纫、零)枕扫。
Swift 提供了8,16辱魁,32和64位的有符號和無符號整數(shù)類型铡原。這些整數(shù)類型和 C 語言的命名方式很像偷厦,比如8位無符號整數(shù)類型是UInt8商叹,32位有符號整數(shù)類型是 Int32 燕刻。就像 Swift 的其他類型一樣,整數(shù)類型采用大寫命名法剖笙。
整數(shù)范圍
你可以訪問不同整數(shù)類型的 min 和 max 屬性來獲取對應(yīng)類型的最小值和最大值:
let minValue = UInt8.min // minValue 為 0卵洗,是 UInt8 類型
let maxValue = UInt8.max // maxValue 為 255,是 UInt8 類型
min 和 max 所傳回值的類型弥咪,正是其所對的整數(shù)類型(如上例UInt8, 所傳回的類型是UInt8)过蹂,可用在表達式中相同類型值旁。
Int
一般來說聚至,你不需要專門指定整數(shù)的長度酷勺。Swift 提供了一個特殊的整數(shù)類型Int,長度與當(dāng)前平臺的原生字長相同:
在32位平臺上扳躬,Int 和 Int32 長度相同脆诉。
在64位平臺上,Int 和 Int64 長度相同贷币。
除非你需要特定長度的整數(shù)击胜,一般來說使用 Int 就夠了。這可以提高代碼一致性和可復(fù)用性役纹。即使是在32位平臺上偶摔,Int 可以存儲的整數(shù)范圍也可以達到 -2,147,483,648 ~ 2,147,483,647 ,大多數(shù)時候這已經(jīng)足夠大了促脉。
UInt
Swift 也提供了一個特殊的無符號類型 UInt辰斋,長度與當(dāng)前平臺的原生字長相同:
在32位平臺上,UInt 和 UInt32 長度相同瘸味。
在64位平臺上宫仗,UInt 和 UInt64 長度相同。
注意:
盡量不要使用UInt硫戈,除非你真的需要存儲一個和當(dāng)前平臺原生字長相同的無符號整數(shù)锰什。除了這種情況,最好使用Int丁逝,即使你要存儲的值已知是非負(fù)的汁胆。統(tǒng)一使用Int可以提高代碼的可復(fù)用性,避免不同類型數(shù)字之間的轉(zhuǎn)換霜幼,并且匹配數(shù)字的類型推斷嫩码,請參考類型安全和類型推斷。
浮點數(shù)
浮點數(shù)是有小數(shù)部分的數(shù)字罪既,比如 3.14159 铸题,0.1 和 -273.15铡恕。
浮點類型比整數(shù)類型表示的范圍更大,可以存儲比 Int 類型更大或者更小的數(shù)字丢间。Swift 提供了兩種有符號浮點數(shù)類型:
Double表示64位浮點數(shù)探熔。當(dāng)你需要存儲很大或者很高精度的浮點數(shù)時請使用此類型。
Float表示32位浮點數(shù)烘挫。精度要求不高的話可以使用此類型诀艰。
注意:
Double精確度很高,至少有15位數(shù)字饮六,而Float只有6位數(shù)字其垄。選擇哪個類型取決于你的代碼需要處理的值的范圍,在兩種類型都匹配的情況下卤橄,將優(yōu)先選擇 Double绿满。
類型安全和類型推斷
Swift 是一個類型安全(type safe)的語言。類型安全的語言可以讓你清楚地知道代碼要處理的值的類型窟扑。如果你的代碼需要一個String喇颁,你絕對不可能不小心傳進去一個Int。
由于 Swift 是類型安全的辜膝,所以它會在編譯你的代碼時進行類型檢查(type checks)无牵,并把不匹配的類型標(biāo)記為錯誤。這可以讓你在開發(fā)的時候盡早發(fā)現(xiàn)并修復(fù)錯誤厂抖。
當(dāng)你要處理不同類型的值時茎毁,類型檢查可以幫你避免錯誤。然而忱辅,這并不是說你每次聲明常量和變量的時候都需要顯式指定類型七蜘。如果你沒有顯式指定類型,Swift 會使用類型推斷(type inference)來選擇合適的類型墙懂。有了類型推斷橡卤,編譯器可以在編譯代碼的時候自動推斷出表達式的類型。原理很簡單损搬,只要檢查你賦的值即可碧库。
因為有類型推斷,和 C 或者 Objective-C 比起來 Swift 很少需要聲明類型巧勤。常量和變量雖然需要明確類型嵌灰,但是大部分工作并不需要你自己來完成。
當(dāng)你聲明常量或者變量并賦初值的時候類型推斷非常有用颅悉。當(dāng)你在聲明常量或者變量的時候賦給它們一個字面量(literal value 或 literal)即可觸發(fā)類型推斷沽瞭。(字面量就是會直接出現(xiàn)在你代碼中的值,比如 42 和 3.14159 剩瓶。)
例如驹溃,如果你給一個新常量賦值 42 并且沒有標(biāo)明類型城丧,Swift 可以推斷出常量類型是 Int ,因為你給它賦的初始值看起來像一個整數(shù):
let meaningOfLife = 42
// meaningOfLife 會被推測為 Int 類型
同理豌鹤,如果你沒有給浮點字面量標(biāo)明類型亡哄,Swift 會推斷你想要的是 Double:
let pi = 3.14159
// pi 會被推測為 Double 類型
當(dāng)推斷浮點數(shù)的類型時,Swift 總是會選擇 Double 而不是Float傍药。
如果表達式中同時出現(xiàn)了整數(shù)和浮點數(shù)磺平,會被推斷為 Double 類型:
let anotherPi = 3 + 0.14159
// anotherPi 會被推測為 Double 類型
原始值 3 沒有顯式聲明類型,而表達式中出現(xiàn)了一個浮點字面量拐辽,所以表達式會被推斷為 Double 類型。
數(shù)值型字面量
整數(shù)字面量可以被寫作:
一個十進制數(shù)擦酌,沒有前綴
一個二進制數(shù)俱诸,前綴是0b
一個八進制數(shù),前綴是0o
一個十六進制數(shù)赊舶,前綴是0x
下面的所有整數(shù)字面量的十進制值都是17:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二進制的17
let octalInteger = 0o21 // 八進制的17
let hexadecimalInteger = 0x11 // 十六進制的17
浮點字面量可以是十進制(沒有前綴)或者是十六進制(前綴是 0x )睁搭。小數(shù)點兩邊必須有至少一個十進制數(shù)字(或者是十六進制的數(shù)字)。十進制浮點數(shù)也可以有一個可選的指數(shù)(exponent)笼平,通過大寫或者小寫的 e 來指定园骆;十六進制浮點數(shù)必須有一個指數(shù),通過大寫或者小寫的 p 來指定寓调。
如果一個十進制數(shù)的指數(shù)為 exp锌唾,那這個數(shù)相當(dāng)于基數(shù)和10^exp的乘積:
1.25e2 表示 1.25 × 10^2,等于 125.0夺英。
1.25e-2 表示 1.25 × 10^-2晌涕,等于 0.0125。
如果一個十六進制數(shù)的指數(shù)為exp痛悯,那這個數(shù)相當(dāng)于基數(shù)和2^exp的乘積:
0xFp2 表示 15 × 2^2余黎,等于 60.0。
0xFp-2 表示 15 × 2^-2载萌,等于 3.75惧财。
下面的這些浮點字面量都等于十進制的12.1875:
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
數(shù)值類字面量可以包括額外的格式來增強可讀性。整數(shù)和浮點數(shù)都可以添加額外的零并且包含下劃線扭仁,并不會影響字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
數(shù)值型類型轉(zhuǎn)換
通常來講垮衷,即使代碼中的整數(shù)常量和變量已知非負(fù),也請使用Int類型斋枢×泵遥總是使用默認(rèn)的整數(shù)類型可以保證你的整數(shù)常量和變量可以直接被復(fù)用并且可以匹配整數(shù)類字面量的類型推斷。
只有在必要的時候才使用其他整數(shù)類型瓤帚,比如要處理外部的長度明確的數(shù)據(jù)或者為了優(yōu)化性能描姚、內(nèi)存占用等等涩赢。使用顯式指定長度的類型可以及時發(fā)現(xiàn)值溢出并且可以暗示正在處理特殊數(shù)據(jù)。
整數(shù)轉(zhuǎn)換
不同整數(shù)類型的變量和常量可以存儲不同范圍的數(shù)字轩勘。Int8類型的常量或者變量可以存儲的數(shù)字范圍是-128127筒扒,而UInt8類型的常量或者變量能存儲的數(shù)字范圍是0255。如果數(shù)字超出了常量或者變量可存儲的范圍绊寻,編譯的時候會報錯:
let cannotBeNegative: UInt8 = -1
// UInt8 類型不能存儲負(fù)數(shù)花墩,所以會報錯
let tooBig: Int8 = Int8.max + 1
// Int8 類型不能存儲超過最大值的數(shù),所以會報錯
由于每種整數(shù)類型都可以存儲不同范圍的值澄步,所以你必須根據(jù)不同情況選擇性使用數(shù)值型類型轉(zhuǎn)換冰蘑。這種選擇性使用的方式,可以預(yù)防隱式轉(zhuǎn)換的錯誤并讓你的代碼中的類型轉(zhuǎn)換意圖變得清晰村缸。
要將一種數(shù)字類型轉(zhuǎn)換成另一種祠肥,你要用當(dāng)前值來初始化一個期望類型的新數(shù)字,這個數(shù)字的類型就是你的目標(biāo)類型梯皿。在下面的例子中仇箱,常量twoThousand是UInt16類型,然而常量one是UInt8類型东羹。它們不能直接相加剂桥,因為它們類型不同。所以要調(diào)用UInt16(one)來創(chuàng)建一個新的UInt16數(shù)字并用one的值來初始化属提,然后使用這個新數(shù)字來計算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
現(xiàn)在兩個數(shù)字的類型都是 UInt16权逗,可以進行相加。目標(biāo)常量 twoThousandAndOne 的類型被推斷為 UInt16垒拢,因為它是兩個 UInt16 值的和旬迹。
SomeType(ofInitialValue) 是調(diào)用 Swift 構(gòu)造器并傳入一個初始值的默認(rèn)方法。在語言內(nèi)部求类,UInt16 有一個構(gòu)造器奔垦,可以接受一個UInt8類型的值,所以這個構(gòu)造器可以用現(xiàn)有的 UInt8 來創(chuàng)建一個新的 UInt16尸疆。注意椿猎,你并不能傳入任意類型的值,只能傳入 UInt16 內(nèi)部有對應(yīng)構(gòu)造器的值寿弱。不過你可以擴展現(xiàn)有的類型來讓它可以接收其他類型的值(包括自定義類型)犯眠,請參考擴展。
整數(shù)和浮點數(shù)轉(zhuǎn)換
整數(shù)和浮點數(shù)的轉(zhuǎn)換必須顯式指定類型:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159症革,所以被推測為 Double 類型
這個例子中筐咧,常量 three 的值被用來創(chuàng)建一個 Double 類型的值,所以加號兩邊的數(shù)類型須相同。如果不進行轉(zhuǎn)換量蕊,兩者無法相加铺罢。
浮點數(shù)到整數(shù)的反向轉(zhuǎn)換同樣行,整數(shù)類型可以用 Double 或者 Float 類型來初始化:
let integerPi = Int(pi)
// integerPi 等于 3残炮,所以被推測為 Int 類型
當(dāng)用這種方式來初始化一個新的整數(shù)值時韭赘,浮點值會被截斷。也就是說 4.75 會變成 4势就,-3.9 會變成 -3泉瞻。
注意:
結(jié)合數(shù)字類常量和變量不同于結(jié)合數(shù)字類字面量。字面量3可以直接和字面量0.14159相加苞冯,因為數(shù)字字面量本身沒有明確的類型袖牙。它們的類型只在編譯器需要求值的時候被推測。
類型別名
類型別名(type aliases)就是給現(xiàn)有類型定義另一個名字抱完。你可以使用typealias關(guān)鍵字來定義類型別名贼陶。
當(dāng)你想要給現(xiàn)有類型起一個更有意義的名字時,類型別名非常有用巧娱。假設(shè)你正在處理特定長度的外部資源的數(shù)據(jù):
typealias AudioSample = UInt16
定義了一個類型別名之后,你可以在任何使用原始名的地方使用別名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 現(xiàn)在是 0
本例中烘贴,AudioSample被定義為UInt16的一個別名禁添。因為它是別名,AudioSample.min實際上是UInt16.min桨踪,所以會給maxAmplitudeFound賦一個初值0老翘。
布爾值
Swift 有一個基本的布爾(Boolean)類型,叫做Bool锻离。布爾值指邏輯上的值铺峭,因為它們只能是真或者假。Swift 有兩個布爾常量汽纠,true 和 false:
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange 和 turnipsAreDelicious 的類型會被推斷為 Bool卫键,因為它們的初值是布爾字面量。就像之前提到的 Int 和 Double 一樣虱朵,如果你創(chuàng)建變量的時候給它們賦值 true 或者 false莉炉,那你不需要將常量或者變量聲明為 Bool 類型。初始化常量或者變量的時候如果所賦的值類型已知碴犬,就可以觸發(fā)類型推斷絮宁,這讓 Swift 代碼更加簡潔并且可讀性更高。
當(dāng)你編寫條件語句比如 if 語句的時候服协,布爾值非常有用:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// 輸出 "Eww, turnips are horrible."
條件語句绍昂,例如if,請參考控制流。
如果你在需要使用 Bool 類型的地方使用了非布爾值窘游,Swift 的類型安全機制會報錯唠椭。下面的例子會報告一個編譯時錯誤:
let i = 1
if i {
// 這個例子不會通過編譯,會報錯
}
然而张峰,下面的例子是合法的:
let i = 1
if i == 1 {
// 這個例子會編譯成功
}
i == 1 的比較結(jié)果是 Bool 類型泪蔫,所以第二個例子可以通過類型檢查。類似 i == 1 這樣的比較喘批,請參考基本操作符撩荣。
和 Swift 中的其他類型安全的例子一樣,這個方法可以避免錯誤并保證這塊代碼的意圖總是清晰的饶深。
元組
元組(tuples)把多個值組合成一個復(fù)合值餐曹。元組內(nèi)的值可以是任意類型,并不要求是相同類型敌厘。
下面這個例子中台猴,(404, "Not Found") 是一個描述 HTTP 狀態(tài)碼(HTTP status code)的元組。HTTP 狀態(tài)碼是當(dāng)你請求網(wǎng)頁的時候 web 服務(wù)器返回的一個特殊值俱两。如果你請求的網(wǎng)頁不存在就會返回一個 404 Not Found 狀態(tài)碼饱狂。
let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")
(404, "Not Found") 元組把一個 Int 值和一個 String 值組合起來表示 HTTP 狀態(tài)碼的兩個部分:一個數(shù)字和一個人類可讀的描述宪彩。這個元組可以被描述為“一個類型為 (Int, String) 的元組”休讳。
你可以把任意順序的類型組合成一個元組,這個元組可以包含所有類型尿孔。只要你想俊柔,你可以創(chuàng)建一個類型為 (Int, Int, Int) 或者 (String, Bool) 或者其他任何你想要的組合的元組。
你可以將一個元組的內(nèi)容分解(decompose)成單獨的常量和變量活合,然后你就可以正常使用它們了:
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"
如果你只需要一部分元組值雏婶,分解的時候可以把要忽略的部分用下劃線(_)標(biāo)記:
let (justTheStatusCode, _) = http404Error
print("The status code is (justTheStatusCode)")
// 輸出 "The status code is 404"
此外,你還可以通過下標(biāo)來訪問元組中的單個元素白指,下標(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"
你可以在定義元組的時候給單個元素命名:
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ù)返回值時,元組非常有用侵续。一個用來獲取網(wǎng)頁的函數(shù)可能會返回一個 (Int, String) 元組來描述是否獲取成功倔丈。和只能返回一個類型的值比較起來,一個包含兩個不同類型值的元組可以讓函數(shù)的返回信息更有用状蜗。請參考函數(shù)參數(shù)與返回值需五。
注意:
元組在臨時組織值的時候很有用,但是并不適合創(chuàng)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu)轧坎。如果你的數(shù)據(jù)結(jié)構(gòu)并不是臨時使用宏邮,請使用類或者結(jié)構(gòu)體而不是元組。請參考類和結(jié)構(gòu)體。
可選類型
使用可選類型(optionals)來處理值可能缺失的情況蜜氨⌒瞪福可選類型表示:
有值,等于 x
或者
沒有值
注意:
C 和 Objective-C 中并沒有可選類型這個概念飒炎。最接近的是 Objective-C 中的一個特性埋哟,一個方法要不返回一個對象要不返回nil,nil表示“缺少一個合法的對象”郎汪。然而赤赊,這只對對象起作用——對于結(jié)構(gòu)體,基本的 C 類型或者枚舉類型不起作用煞赢。對于這些類型抛计,Objective-C 方法一般會返回一個特殊值(比如NSNotFound)來暗示值缺失。這種方法假設(shè)方法的調(diào)用者知道并記得對特殊值進行判斷照筑。然而吹截,Swift 的可選類型可以讓你暗示任意類型的值缺失,并不需要一個特殊值凝危。
來看一個例子波俄。Swift 的 Int 類型有一種構(gòu)造器,作用是將一個 String 值轉(zhuǎn)換成一個 Int 值蛾默。然而弟断,并不是所有的字符串都可以轉(zhuǎn)換成一個整數(shù)。字符串 "123" 可以被轉(zhuǎn)換成數(shù)字 123 趴生,但是字符串 "hello, world" 不行。
下面的例子使用這種構(gòu)造器來嘗試將一個 String 轉(zhuǎn)換成 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測為類型 "Int?"昏翰, 或者類型 "optional Int"
因為該構(gòu)造器可能會失敗苍匆,所以它返回一個可選類型(optional)Int,而不是一個 Int棚菊。一個可選的 Int 被寫作 Int? 而不是 Int浸踩。問號暗示包含的值是可選類型,也就是說可能包含 Int 值也可能不包含值统求。(不能包含其他任何值比如 Bool 值或者 String 值检碗。只能是 Int 或者什么都沒有。)
nil
你可以給可選變量賦值為nil來表示它沒有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一個可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值
注意:
nil不能用于非可選的常量和變量码邻。如果你的代碼中有常量或者變量需要處理值缺失的情況折剃,請把它們聲明成對應(yīng)的可選類型。
如果你聲明一個可選常量或者變量但是沒有賦值像屋,它們會自動被設(shè)置為 nil:
var surveyAnswer: String?
// surveyAnswer 被自動設(shè)置為 nil
注意:
Swift 的 nil 和 Objective-C 中的 nil 并不一樣怕犁。在 Objective-C 中,nil 是一個指向不存在對象的指針。在 Swift 中奏甫,nil 不是指針——它是一個確定的值戈轿,用來表示值缺失。任何類型的可選狀態(tài)都可以被設(shè)置為 nil阵子,不只是對象類型思杯。
if 語句以及強制解析
你可以使用 if 語句和 nil 比較來判斷一個可選值是否包含值。你可以使用“相等”(==)或“不等”(!=)來執(zhí)行比較挠进。
如果可選類型有值色乾,它將不等于 nil:
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 輸出 "convertedNumber contains some integer value."
當(dāng)你確定可選類型確實包含值之后,你可以在可選的名字后面加一個感嘆號(!)來獲取值奈梳。這個驚嘆號表示“我知道這個可選有值杈湾,請使用它∪列耄”這被稱為可選值的強制解析(forced unwrapping):
if convertedNumber != nil {
print("convertedNumber has an integer value of (convertedNumber!).")
}
// 輸出 "convertedNumber has an integer value of 123."
更多關(guān)于 if 語句的內(nèi)容漆撞,請參考控制流。
注意:
使用 ! 來獲取一個不存在的可選值會導(dǎo)致運行時錯誤于宙。使用 ! 來強制解析值之前浮驳,一定要確定可選包含一個非 nil 的值。
可選綁定
使用可選綁定(optional binding)來判斷可選類型是否包含值捞魁,如果包含就把值賦給一個臨時常量或者變量至会。可選綁定可以用在 if 和 while 語句中谱俭,這條語句不僅可以用來判斷可選類型中是否有值奉件,同時可以將可選類型中的值賦給一個常量或者變量。if 和 while 語句昆著,請參考控制流县貌。
像下面這樣在 if 語句中寫一個可選綁定:
if let constantName = someOptional {
statements
}
你可以像上面這樣使用可選綁定來重寫 possibleNumber 這個例子:
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 包含一個值,創(chuàng)建一個叫做 actualNumber 的新常量并將可選包含的值賦給它凑懂∶汉郏”
如果轉(zhuǎn)換成功,actualNumber 常量可以在 if 語句的第一個分支中使用接谨。它已經(jīng)被可選類型 包含的 值初始化過摆碉,所以不需要再使用 ! 后綴來獲取它的值。在這個例子中脓豪,actualNumber 只被用來輸出轉(zhuǎn)換結(jié)果巷帝。
你可以在可選綁定中使用常量和變量。如果你想在if語句的第一個分支中操作 actualNumber 的值跑揉,你可以改成 if var actualNumber锅睛,這樣可選類型包含的值就會被賦給一個變量而非常量埠巨。
你可以包含多個可選綁定或多個布爾條件在一個 if 語句中,只要使用逗號分開就行现拒。只要有任意一個可選綁定的值為nil辣垒,或者任意一個布爾條件為false,則整個if條件判斷為false印蔬,這時你就需要使用嵌套 if 條件語句來處理勋桶,如下所示:
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("(firstNumber) < (secondNumber) < 100")
}
// 輸出 "4 < 42 < 100"
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("(firstNumber) < (secondNumber) < 100")
}
}
}
// 輸出 "4 < 42 < 100"
注意: 在 if 條件語句中使用常量和變量來創(chuàng)建一個可選綁定,僅在 if 語句的句中(body)中才能獲取到值侥猬。相反例驹,在 guard 語句中使用常量和變量來創(chuàng)建一個可選綁定,僅在 guard 語句外且在語句后才能獲取到值退唠,請參考提前退出鹃锈。
隱式解析可選類型
如上所述,可選類型暗示了常量或者變量可以“沒有值”瞧预∈赫可選可以通過 if 語句來判斷是否有值,如果有值的話可以通過可選綁定來解析值垢油。
有時候在程序架構(gòu)中盆驹,第一次被賦值之后,可以確定一個可選類型總會有值滩愁。在這種情況下躯喇,每次都要判斷和解析可選值是非常低效的,因為可以確定它總會有值硝枉。
這種類型的可選狀態(tài)被定義為隱式解析可選類型(implicitly unwrapped optionals)廉丽。把想要用作可選的類型的后面的問號(String?)改成感嘆號(String!)來聲明一個隱式解析可選類型。
當(dāng)可選類型被第一次賦值之后就可以確定之后一直有值的時候妻味,隱式解析可選類型非常有用雅倒。隱式解析可選類型主要被用在 Swift 中類的構(gòu)造過程中,請參考無主引用以及隱式解析可選屬性弧可。
一個隱式解析可選類型其實就是一個普通的可選類型,但是可以被當(dāng)做非可選類型來使用劣欢,并不需要每次都使用解析來獲取可選值棕诵。下面的例子展示了可選類型 String 和隱式解析可選類型 String 之間的區(qū)別:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感嘆號來獲取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感嘆號
你可以把隱式解析可選類型當(dāng)做一個可以自動解析的可選類型。你要做的只是聲明的時候把感嘆號放到類型的結(jié)尾凿将,而不是每次取值的可選名字的結(jié)尾校套。
注意:
如果你在隱式解析可選類型沒有值的時候嘗試取值,會觸發(fā)運行時錯誤牧抵。和你在沒有值的普通可選類型后面加一個驚嘆號一樣笛匙。
你仍然可以把隱式解析可選類型當(dāng)做普通可選類型來判斷它是否包含值:
if assumedString != nil {
print(assumedString)
}
// 輸出 "An implicitly unwrapped optional string."
你也可以在可選綁定中使用隱式解析可選類型來檢查并解析它的值:
if let definiteString = assumedString {
print(definiteString)
}
// 輸出 "An implicitly unwrapped optional string."
注意:
如果一個變量之后可能變成nil的話請不要使用隱式解析可選類型侨把。如果你需要在變量的生命周期中判斷是否是nil的話,請使用普通可選類型妹孙。
錯誤處理
你可以使用 錯誤處理(error handling) 來應(yīng)對程序執(zhí)行中可能會遇到的錯誤條件秋柄。
相對于可選中運用值的存在與缺失來表達函數(shù)的成功與失敗,錯誤處理可以推斷失敗的原因蠢正,并傳播至程序的其他部分骇笔。
當(dāng)一個函數(shù)遇到錯誤條件,它能報錯嚣崭。調(diào)用函數(shù)的地方能拋出錯誤消息并合理處理笨触。
func canThrowAnError() throws {
// 這個函數(shù)有可能拋出錯誤
}
一個函數(shù)可以通過在聲明中添加throws關(guān)鍵詞來拋出錯誤消息。當(dāng)你的函數(shù)能拋出錯誤消息時, 你應(yīng)該在表達式中前置try關(guān)鍵詞雹舀。
do {
try canThrowAnError()
// 沒有錯誤消息拋出
} catch {
// 有一個錯誤消息拋出
}
一個do語句創(chuàng)建了一個新的包含作用域,使得錯誤能被傳播到一個或多個catch從句芦劣。
這里有一個錯誤處理如何用來應(yīng)對不同錯誤條件的例子。
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
在此例中说榆,makeASandwich()(做一個三明治)函數(shù)會拋出一個錯誤消息如果沒有干凈的盤子或者某個原料缺失虚吟。因為 makeASandwich() 拋出錯誤,函數(shù)調(diào)用被包裹在 try 表達式中娱俺。將函數(shù)包裹在一個 do 語句中稍味,任何被拋出的錯誤會被傳播到提供的 catch 從句中。
如果沒有錯誤被拋出荠卷,eatASandwich() 函數(shù)會被調(diào)用模庐。如果一個匹配 SandwichError.outOfCleanDishes 的錯誤被拋出,washDishes() 函數(shù)會被調(diào)用油宜。如果一個匹配 SandwichError.missingIngredients 的錯誤被拋出掂碱,buyGroceries(_:) 函數(shù)會被調(diào)用,并且使用 catch 所捕捉到的關(guān)聯(lián)值 [String] 作為參數(shù)慎冤。
拋出疼燥,捕捉,以及傳播錯誤會在錯誤處理章節(jié)詳細(xì)說明蚁堤。
斷言和先決條件
斷言和先決條件是在運行時所做的檢查醉者。你可以用他們來檢查在執(zhí)行后續(xù)代碼之前是否一個必要的條件已經(jīng)被滿足了。如果斷言或者先決條件中的布爾條件評估的結(jié)果為 true(真)披诗,則代碼像往常一樣繼續(xù)執(zhí)行撬即。如果布爾條件評估結(jié)果為false(假),程序的當(dāng)前狀態(tài)是無效的呈队,則代碼執(zhí)行結(jié)束剥槐,應(yīng)用程序中止。
你使用斷言和先決條件來表達你所做的假設(shè)和你在編碼時候的期望宪摧。你可以將這些包含在你的代碼中粒竖。斷言幫助你在開發(fā)階段找到錯誤和不正確的假設(shè)颅崩,先決條件幫助你在生產(chǎn)環(huán)境中探測到存在的問題。
除了在運行時驗證你的期望值蕊苗,斷言和先決條件也變成了一個在你的代碼中的有用的文檔形式沿后。和在上面討論過的錯誤處理不同,斷言和先決條件并不是用來處理可以恢復(fù)的或者可預(yù)期的錯誤岁歉。因為一個斷言失敗表明了程序正處于一個無效的狀態(tài)得运,沒有辦法去捕獲一個失敗的斷言。
使用斷言和先決條件不是一個能夠避免出現(xiàn)程序出現(xiàn)無效狀態(tài)的編碼方法锅移。然而熔掺,如果一個無效狀態(tài)程序產(chǎn)生了,斷言和先決條件可以強制檢查你的數(shù)據(jù)和程序狀態(tài)非剃,使得你的程序可預(yù)測的中止(譯者:不是系統(tǒng)強制的置逻,被動的中止),并幫助使這個問題更容易調(diào)試备绽。一旦探測到無效的狀態(tài)券坞,執(zhí)行則被中止,防止無效的狀態(tài)導(dǎo)致的進一步對于系統(tǒng)的傷害肺素。
斷言和先決條件的不同點是恨锚,他們什么時候進行狀態(tài)檢測:斷言僅在調(diào)試環(huán)境運行,而先決條件則在調(diào)試環(huán)境和生產(chǎn)環(huán)境中運行倍靡。在生產(chǎn)環(huán)境中猴伶,斷言的條件將不會進行評估。這個意味著你可以使用很多斷言在你的開發(fā)階段塌西,但是這些斷言在生產(chǎn)環(huán)境中不會產(chǎn)生任何影響他挎。
使用斷言進行調(diào)試
你可以調(diào)用 Swift 標(biāo)準(zhǔn)庫的 assert(::file:line:) 函數(shù)來寫一個斷言。向這個函數(shù)傳入一個結(jié)果為 true 或者 false 的表達式以及一條信息捡需,當(dāng)表達式的結(jié)果為 false 的時候這條信息會被顯示:
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因為 age < 0办桨,所以斷言會觸發(fā)
在這個例子中,只有 age >= 0 為 true 時站辉,即 age 的值非負(fù)的時候呢撞,代碼才會繼續(xù)執(zhí)行。如果 age 的值是負(fù)數(shù)饰剥,就像代碼中那樣狸相,age >= 0 為 false,斷言被觸發(fā)捐川,終止應(yīng)用。
如果不需要斷言信息逸尖,可以就像這樣忽略掉:
assert(age >= 0)
如果代碼已經(jīng)檢查了條件古沥,你可以使用 assertionFailure(_:file:line:)函數(shù)來表明斷言失敗了瘸右,例如:
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
強制執(zhí)行先決條件
當(dāng)一個條件可能為false(假),但是繼續(xù)執(zhí)行代碼要求條件必須為true(真)的時候岩齿,需要使用先決條件太颤。例如使用先決條件來檢查是否下標(biāo)越界,或者來檢查是否將一個正確的參數(shù)傳給函數(shù)盹沈。
你可以使用全局 precondition(::file:line:) 函數(shù)來寫一個先決條件龄章。向這個函數(shù)傳入一個結(jié)果為 true 或者 false 的表達式以及一條信息,當(dāng)表達式的結(jié)果為 false 的時候這條信息會被顯示:
// 在一個下標(biāo)的實現(xiàn)里...
precondition(index > 0, "Index must be greater than zero.")
你可以調(diào)用 precondition(::file:line:)方法來表明出現(xiàn)了一個錯誤乞封,例如做裙,switch 進入了 default 分支,但是所有的有效值應(yīng)該被任意一個其他分支(非 default 分支)處理肃晚。
注意:
如果你使用unchecked模式(-Ounchecked)編譯代碼锚贱,先決條件將不會進行檢查。編譯器假設(shè)所有的先決條件總是為true(真)关串,他將優(yōu)化你的代碼拧廊。然而,fatalError(:file:line:)函數(shù)總是中斷執(zhí)行晋修,無論你怎么進行優(yōu)化設(shè)定吧碾。
你能使用 fatalError(:file:line:)函數(shù)在設(shè)計原型和早期開發(fā)階段,這個階段只有方法的聲明墓卦,但是沒有具體實現(xiàn)倦春,你可以在方法體中寫上fatalError("Unimplemented")作為具體實現(xiàn)。因為fatalError不會像斷言和先決條件那樣被優(yōu)化掉趴拧,所以你可以確保當(dāng)代碼執(zhí)行到一個沒有被實現(xiàn)的方法時溅漾,程序會被中斷。