Swift語言開發指南
基礎部分
Swift是一門新的開發語言,它可以在iOS、macOS watchOS以及tvOS系統環境下進行應用的開發。
Swift提供了它自己的C
和Objective-C
語言的所有基本數據類型。包括用于描述整數的Int,描述浮點型的Double
和Float
,描述布爾值的Bool
以及描述文本數據的String
。Swift也提供了三個主要的集合類型。比如集合類型中描述的的數組、集合、和字典。
和C語言一樣,Swift用變量來存儲和引用一個已經被聲明過名字的值。Swift同樣能夠使用不可變數據類型的值。這些不能改變的值被稱為常量,它比C語言中的常量更加強大。當你在使用不可變的值的時候,常量能夠使你的代碼變得安全整潔。
除了一些熟悉的類型之外,Swift還提供了Objective-C
沒有的高級類型,比如說:元組。元組能夠讓你創創建和傳遞一組數據。函數中返回多個值的時候你可以用元組作為單個復合值來接收。
Swift也提供了一些可選類型,它能夠處理值缺失的情況。可選值的意思是說:這里有一個值,它等于X
或者它沒有值
。使用可選類型的之后就像使用Objective-C
中的空指針一樣,但是它的使用不僅僅局限于類,可以是任意類型。和Objective-C
中的空指針相比來說,可選類型可不僅僅是安全和更具表現力那么簡單,它們是Swift最強大功能中的核心。
Swift是一門安全類型的語言。這意味著這門語言可以幫你弄明白你所使用的值是什么類型的。如果你的代碼中需要的是String
,當你用Int
來給它賦值的時候,類型安全會阻止你這么做。同樣的,如果你意外的將可選字符串傳遞給非可選字符串那么類型安全會阻止你這么做。類型安全可以幫你在開發過程中盡早的捕獲和修正錯誤。
常量和變量
常量和變量都需要用一個別名(比如說maximumNumberOfLoginAttempts
或者welcomeMessage
)以及一個特殊的數據類型的值(比如說數字10
和字符串hello
)來來進行關聯.常量的值一旦被初始化設置了之后就不能發生改變了,而變量則可以在未來給它賦不同的值。
常量和變量的聲明
常量和變量在使用之前都是要經過聲明的。你可以用關鍵詞let
來修飾一個常量,用var
關鍵詞來修飾一個變量。下面的例子是通過對常量和變量的使用來模擬一個用戶嘗試登錄次數的場景。
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempts = 0
這段代碼可以被解讀為:
聲明一個新的名字為maximumNumberOfLoginAttempts
的常量,它的值為10。然后聲明一個新的名為currentLoginAttempts
的變量,它的初始值為0.
在這個例子中,最大允許嘗試登錄次數被聲明為一個常量,因為最大的嘗試登錄次數不能被修改。當前的嘗試登錄次數被聲明為一個變量,因為這個次值會隨著嘗試登錄失敗的次數的增加而增加。
你可以連續聲明幾個常量或者變量在同一行中,他們之間用逗號隔開。
var x = 0.0,y = 0.0,z = 0.0
注意:
如果你的代碼中使用的值是不會發生改變的那么用let
關鍵字來聲明。如果聲明的值是需要發生改變的那么用var
關鍵字來聲明。
類型標注
在聲明常量和變量的時候你可以為它提供一個類型標注,它可以很清楚的告訴編輯器常量和變量以什么樣的數據類型被存儲。在常量和變量添加類型標注的時候在命名之后加一個冒號以以及空格,然后加上類型的名稱。
下面的這個例子里給出一個叫做welcomeMessage
變量的類型標注,它表明了這個變量被存儲為一個String
類型的值。
var welcomeMessage: String
在聲明的時候冒號的意思是“...類型的”,因此上面的代碼可以這樣來理解:
聲明一個類型為String
的變量welcomeMessage
。
類型字符串的意思是可以存儲任意值的字符串,可以把它理解為有存儲功能的"事物類型"。
變量welcomeMessage
可以被賦值為任意的字符串而不報錯。
welcomeMessage = "Hello"
你可以在一行定義多個同一種類型的變量,用逗號隔開在最后一個變量后面添加冒號和類型標注。
var red,green,blue:Double
注意:
在實際開發中我們很少需要給常量/變量來寫類型標注,如果在定義常量/變量的時候給了一個初始值,那么Swift可以幫我們推斷出常量/變量的類型。具體請參考類型和安全判斷
。在上面定義的變量welcomeMessage
中,我們沒有給它賦初值,因此變量welcomeMessage
的類型是從類型標注里判斷的,而不是從它初始值判斷的。
常量和變量的命名
常量和變量的名字可以包含任意字符串,當然也包括了Unicode
字符。
let π = 3.14159
let 你好 = "你好世界"
let ???? = "dogcow"
常量和變量的命名不能包含空格字符,數學符號,箭頭,保留的(或者非法的)Unicode碼位,連線與制表符。也不能夠用數字開頭,盡管數字可能在名稱的其他地方可以使用。
一旦你為常量和變量聲明一個確定的類型之后,你不能夠再聲明一個相同名字類型的常量/變量,并且也不能夠改變它所存儲的類型,當然你也不能夠對常量和變量的互換操作。
注意:
如果你需要使用和Swift保留關鍵字相同的名稱作為常量和變量名。你可以使用關鍵字反引號(`)將關鍵字包起來作為它的名字,不到萬不得已的時候,建議你不要使用關鍵字來作為常量和變量的名字。
你可以改變一個變量的值為另外一種相同類型的值,在下面的例子中,friendWelcome
的值從hello!
變為Bonjour!
。
var friendWelcome = "hello !"
friendWelcome = "Bonjour !"
// friendWelcome is now "Bonjour!"
和變量不同的是常量在初始化設置以后就不能夠發生改變了。當你改變常量值的時候,編譯器會報錯。
let languageName = "Swift"
languageName = "c++"
//Cannot assign to value: 'languageName' is a 'let' constant
// 這里是一個編譯錯誤,languageName 不能夠被改變.
打印出常量和變量
你可以用print(_:separator:terminator:)
這個函數打印出常量和變量的當前值。
print(friendWelcome)
// 打印出 friendWelcome 的值
print(_:separator:terminator:)
函數是一個在全局的指定區域打印出一個甚至更多值的的函數。例如在Xcode里面,print(_:separator:terminator:)
是在xcode的控制臺輸出的。separator
和terminator
參數都有默認值,因此你當你調用函數的時候可以省略掉它們。函數默認通過換行符來結束當前行。如果不想換行,可以默認添加一個空的字符串來代替,例如:print(someValue, terminator: "").
如果想了解更多關于參數的信息,可以查看默認參數。
Swift用字符串插值的方式把常量名和變量名當做占位符加入到長字符串中,Swift會把常量和變量的值替換掉這些占位符。把常量和變量名放到括號里面,并在括號前面加上反斜杠來轉義。
print("The currenct value of firendlyWelcome is \(friendWelcome)")
// 打印出當前firendlyWelcome的值
注意:
字符串插值所有能用的選項在這里能找到。
注釋
把你代碼里不用執行的文本用打上注釋作為注解或者一個標記來提醒你自己。在swift中,編譯器會忽略掉注釋的編譯。
在swift中注釋和在c語言的注釋十分相似。單行的注釋用//
來表示。
// This is a comment.
多行注釋以/*
開始,以*/
結尾。
/*
This is also a comment.
but is written over multiple lines.
*/
和C語言中多行注釋不同的是,swift中多行注釋是可以進行嵌套的。你可以寫一個嵌套的多行注釋用/*
開始 ,然后添加第二個多行注釋以/*
開始以*/
結束。最后用*/
來結束第一個多行注釋。
/* This is the start of the first multiline comment.
/* This is the second,nested multiline comment. */
This is the end of the first mulitiline comment.*/
注釋的多行嵌套使得你在注釋大量代碼塊的時候更加便捷和容易,即使代碼里已經存在多行注釋也沒有影響。
分號
和其他編程語言不同的是,在swift中,在代碼的結尾,不需要你寫分號(;)當然,你也可以按照你的習慣來添加分號。有一種情況是必須要添加分號的,那就是在一行中執行多個語句
let cat = "??";print(cat)
// print "??"
整數
整數就是沒有小數的數字,比如說:42和-23.整數可以是有符號的類型(正數,負數和0),也可以是無符號的類型(正數和0)。
swift
為我們提供了8位,16位,32位,以及64位的無符號和有符號的整數。這些整數的命名規則和c語言類似。比如說:有無符號的8位整數UInt8
和有符號的32位整數Int32
。像swift
中的其他類型一樣,整數類的命名都是大寫字母開頭。
整數的范圍
你可以通過訪問整數的最大值屬性和最小值屬性在確定他們的范圍。
let minValue = UInt8.min // UInt8的最小值為0
let maxValue = UInt8.max // UInt8的最大值為2
print("minValue of UInt8 is \(minValue) and maxValue of UInt8 is \(maxValue)。")
這些屬性的值表明了這種類型數據只能在規定范圍內進行操作(就像上面UInt8
的例子),因此同種類型的數據可以在表達式中一起使用。
Int
在大多數情況下,在寫代碼的過程中我們并不需要指定一個長度給Integer
。Swift
提供了另外一個整數類型的數據Int
,它的長度和原生平臺的字節數相同。
- 在32位的平臺上,
Int
的長度和Int32
一致。 - 在64位的平臺上,
Int
的長度和Int64
一致。
如果你不需要為整型添加特殊的長度處理,用默認的Int
來實現代碼就行。這可以提高代碼的一致性和可復用性。甚至是在32位的平臺上,他能夠儲存在-2
,147
,483
,648
和2
,147
,483
,647
范圍之間的數據,在很多時候這個范圍內的數據已經很大了。
UInt
swift
也為我們提供了無符號類型的整型數據。UInt
和原生平臺有著相同長度的字節數。
- 在32位的平臺上,
UInt
的長度和UInt32
一致。 - 在64位的平臺上,
UInt
的長度和UInt64
一致。
注意:
盡量不要使用UInt
,除非你真的需要存儲一個和當前原生平臺長度相同的字節數的無符號整數時候,如果不是這種情況,建議你最好使用Int
,即使你要存儲的對象已知是非負的。統一使用Int
提高了代碼的一致性和可復用性。避免在不同的數據類型進行轉換,并且匹配數字的類型進行判斷,具體請參考類型安全和類型推斷。
浮點型數據
所謂浮點型數據就是帶有小數部分的數據。比如:3.14258
,0.1
和-273.15
。
浮點型所代表值得范圍要比整型要更大,它能夠儲存比整型更小或者更大的值。Swift提供了兩種有符號的浮點數類型。
-
Double
類型代表的64位浮點型數據。 -
Float
類型代表的32位浮點型數據。
注意:
Double
類型精度至少為小數點后15位,Float
類型的精確度僅僅是小數點后6位。
你可以根據自己編程的需要值的范圍選擇是Double
類型還是Float
類型,如果兩種條件都滿足,優先選擇Double
。
類型安全和類型判斷
Swift是一個類型安全的語言,類型安全的語言可以讓你清楚的知道你所處理的代碼值的類型。如果你代碼中需要的是String
,那么。你如果給它賦值為Int
類型的數據,那么編譯器就會報錯。
因為Swift
是類型安全的語言,所以它在編譯的時候會對代碼進行類型的檢查。這在開發過程中能夠幫助你盡可能早的發現和解決問題。
當你在處理不同類型數據的時候,類型檢查能夠幫助避免一些問題。然而,這并不意味著你在每次聲明常量或者變量的時候都需要顯示指定類型。如果你沒有指定顯示類型,那么swift會使用類型判斷來為你選擇合適的類型。類型判斷確保了編譯器在編譯代碼的時候通過檢查你賦的值自動推斷出表達式的類型。
因為有了類型判斷,和c或者c++相比來說,swift很少需要你進行類型聲明。常量和變量雖然需要來明確類型,但是大部分工作并不需要你來完成,編譯器已經為你完成了。
當你聲明一個常量或者變量賦初值的時候類型判斷變非常有用。在你聲明常量或者變量的時候,賦給它們一個字面量(literal value
或者literal
)就能夠讓編譯器自己來進行類型判斷。(所謂字面量就是直接出現在代碼中的值,比如下面例子中的42
和3.14159
。)
例如:如果你給一個新的常量用字面量的形式給它賦值為42
沒有聲明它的數據類型,Swift能推斷出你要給常量賦一個Int
類型的數據,因為你在初始化的時候給它賦值了一個像整型的數字。
let meaningOfLife = 42
// meaningOfLife will be inferred to be of type Int 這里常量meaningOfLife 會被便以及推斷為一個整型
同理,如果你沒有給浮點類型的數據標記類型,那么Swift
會默認類型的Double
let pi = 3.14159
// pi will be inferred to be of type Double 這里常量pi會被默認推斷為DoubleL類型的數據。
當swift在推斷浮點型數據的時候,它會默認推斷為Double
類型而不是Float
類型。
如果你在一個表達式中用整型和浮點型混合計算的時候,在上下文中會被swift
推斷為Double
類型。
let anotherPi = 3 + 0.14159
// anotherPi will be inferred of type Double 這里常量anotherPi會被swift推斷為Double類型的數據。
初始值3沒有給顯式聲明類型,并且在表達式中出現了一個浮點類型的字面量,所以表達式被推斷為Double
類型。
數值型字面量
整數類型的字面量可以被寫作:
- 一個十進制數,沒有前綴
- 一個二進制數,前綴是
0b
- 一個八進制數,前綴是
0o
- 一個十六進制數,前綴是
0X
下面所有的整數型字面量都是代表十進制的值17
let decimalInteger = 17
let binaryInteger = 0b10001// 二進制的17 2*2*2*2+1
let octalInteger = 0o21 // 八進制的17 2*8+1*1
let hexadecimalInteger = 0x11 // 十六進制的17 16*1+1
浮點型的字面量可以是十進制(沒有前綴),也可以是十六進制的(帶有前綴)。在小數的兩遍必須是十進制的值值(或者十六進制有前綴的值)。十進制的浮點數可以有一個可選的指數(exponent)。通常用大寫的或者小寫的e來表示,十六進制的浮點數,通常用大寫或者小寫的p
來表示。
如果一個十進制的指數為exp
,那么這個數相當于基數和10^exp
的乘積.
例如:
1.25e2
表示的是1.25*10^2
或者125.0
。
1.25e-2
表示的是1.25*10^-2
或者0.0125
。
如果一個有前綴的十六進制的指數為exp
,那么這個數相當于基數和2^exp
的乘積。
例如:
0xFp2
表示的是15*2^2
或者60.0
。
0xFp-2
表示的是15*2^-2
或者3.75
。
下面的浮點型的字面量都等價于十進制的值12.1875
。
//浮點類型的十進制的12.1875
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
//計算方式:0x代表16進制C代表12后面.3代表小數 所以整數部分應該是應該是12*(16^0)+0.3*(16^-1)
后面的p代表指數,以2為底。所以完整表達式為(12*(16^0)+0.3*(16^-1))*(2^0)
數字類型的字面量添加額外的標記能夠使它們看起來更容易閱讀。整數和浮點型都可以添加額外的零加上下劃線來增強可閱讀性,并且不會影響字面量的值。
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_000_1
數值類型轉換
即使常量和變量你已經知道它們是非負的,你也可以在代碼中用Int
類型來修飾它們。使用默認整數數據類型保證了常量和變量可以被復用并且能用來匹配整數字面量的類型推斷。
當我們做特殊的任務時候才會對整型類型做類型指定,比如:需要處理的外部的長度明確了數據或者為了性能優化、內存占用等等。使用顯式指定長度的類型可以及時的發現值的溢出并且還能標記我們在處理的是特殊的數據。
整數類型轉換
不同類型的整數可以保存不同范圍的常量和變量。Int8類型的整數可以存儲數據的范圍是-128
到127
。無符號整型的可以存儲常量和變量的范圍是0
到255
。如果數字超出了常量和變量對應值的范圍,編譯的時候會報錯。
let cannotBeNegative:UInt8 = -1 //不在UInt8值范圍內報錯
let tooBig:Int8 = Int8.max + 1 //超出Int8值范圍內報錯
因為不同數據類型能夠存儲數據的范圍是不一樣的,在進行類型轉換的時候你必須選擇一個合適的數據類型進行轉換。這種轉換方式能夠讓你的代碼的意圖更明顯,并且能夠防止你在隱式轉換中遇到的錯誤。
在轉換一個特殊的數字類型到另一個類型的時候,你需要重新初始化一個你需要的目標數據。在下面的例子中,常量twoThousand
是一個UInt16
類型的數據,和它一起需要轉換的數據類型是UInt8
,它們兩個不能夠直接相加,因為他們兩個類型不一樣。
我們可以用UInt16(one)
來創建一個UInt
類型的數據用one
的值進行初始化。用初始化的數據類代替原始數據。
let twoThousand:UInt16 = 2_000
let one:UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因為等式兩邊的類型都是UInt16
,所以兩者等式操作是被允許的。輸出的常量(twoThousandAndOne)
的數據類型被swift
推斷為UInt16
,因為它是兩個UInt16的常量相加而來的。
SomeType(ofInitialValue)
調用的是swift
構造器并傳入一個初始值的默認方法。在語言的內部,UInt16有一個構造器可以接收一個UInt8
類型的數據。這個構造的作用是通過已經存在的UInt8
類型的數據初始化一個UInt16
類型的數據。需要注意的是:你并不能任意的傳入值,只有在傳入UInt16
內部有對應構造器的值。不過你可以擴展現有的類型,讓它來接收其他類型的值(包括自定義的類型),具體請參考擴展。
整數和浮點數轉換
在進行整數和浮點數類型轉換的時候一定要指定類型。
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi等于3.14159,被Swift推斷為double類型
在這里,常量three
被當做一個Double
類型的數據來創建,因此+
兩邊應為相同的類型。如果這里沒有進行轉化,那么+
(加號運算符)是不被允許使用的。
浮點型的數據和整型一樣可以互相轉換。整型可以使用Double
和Float
進行初始化。
let integerPi = Int(pi)
//這里integerPi等于3,被Swift推斷為Int類型。
上面我們使用浮點型數據進行初始化一個整型數據的時候,我們發現浮點值被截斷了。這意味著4.75變成了4,-3.9被截斷為-3。
注意:
結合數字型的常量和變量的規則和數字字面量的規則是不同的,在字面量3可以和直接和字面量0.14159相加,因為數字型的字面量他們本身值是沒有特定類型的。它們的類型只有在編譯需要計算值的時候才會被推斷。
類型別名
類型別名是給已經存在的一個數據類型添加一個可選的名字。你可以用關鍵字typealias
來定義這是一個類型別名。
當你想給現有的類型添加一個有意義的名字的時候,類型別名顯得特別的有用。我們假設你正在處理特定長度的外部資源的數據的時候:
typealias AudioSample = UInt16
當你定義過一個類型別名的時候,你可以在任意一個地方像使用原始名一樣來使用這個別名。
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 現在的值是0
在這里,AudioSample
被定義成一個值為UInt16
的別名。正因為這是一個別名,所以我們調用AudioSample.min
實際上就是調用UInt16.min
這個函數,這樣為變量maxAmplitudeFound
提供了一個初始值0
。
布爾值
Swift
有一個基礎的布爾類型叫做Bool
,布爾的值是邏輯上的值,因此只有真和假。Swift
為我們提供了兩個bool常量true
和false
。
let ornagesAndOrange = true
let turnipsAndDelicious = false
ornagesAndOrange
和turnipsAndDelicious
被推斷為Bool
因為他們是由Bool
字面量初始化來的。就像上面提到的Int
和Double
類型,當在聲明的時候你只需要設置一下他們的值true
或false
不需要聲明常量或者變量的類型。當初始化常量或者變量的時候如果要賦值的類型是已知的,就可以觸發便以及的類型推斷,這使得Swift的代碼更加簡潔和易讀。
當你在寫條件語句比如if
語句的時候,布爾值顯得更加有用。
if turnipsAndDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// 這里打印的是:Eww, turnips are horrible.
關于條件語句,比如if
語句,可以參考控制流
如果你在使用Bool值的時候賦了其他類型的值,那么swift因為類型安全就會報錯。下面的例子中就會報編譯的錯誤。
let i= 1
if i {
// 這個例子不會編譯成功,會報錯(報錯原因是 判斷的值不是bool類型)
}
然而,下面的例子是合法的:
let i = 1
if i == 1 {
// 這個例子能編譯成功不報錯。因為這里i == 1 是一個判斷語句
}
因為等式i == 1
的結果是一個Bool
類型的值,所以第二個例子能夠通過類型檢查。像i == 1
這樣類型的比較,參考 基本操作符。
和swift中其他類型安全的例子一樣,這個方法避免了一些偶然的錯誤并且保證了代碼的目的總是清晰的。
元組
元組(Truples)就是把多個值組合成一個復合值。元組中的成員并不一定是相同類型的數據,它們可以是任意類型的數據。
在這個例子中(404,"Not Found")
就是一個代表HTTP
狀態碼的元組。HTTP
狀態碼是當你請求網頁的時候,web服務器返回的一個特殊值。如果請求的網頁不存在就會返回狀態碼404 NOT Found
。
let http404Error = (404,"Not Found")
// http404Error的類型是(Int,String)的元組,值是(404, "Not Found")
(404,"Not Found")
這個元組把Int
和String
的值放到一起組合起來表示HTTP
請求的狀態碼的兩部分:一個數組和另外一個可讀的描述。它被描述為一個類型為(Int,String)
的元組。
你可以把任意順序的類型組合為一個元組,這個元組可以你需要的任意類型的數據。你可以創建一個類型為(Int,Int,Int)
或者(String,Bool)
或者其他的任何你想創建任意組合的元組。
你可以將元組的內容分解為單獨的常量或者變量,然后你就可以正常使用它們了。
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
//輸出的狀態碼為 404
print("The status message is \(statusMessage)")
// 輸出的狀態描述為 Not Found
如果你只需要元組中的其中一個值,那么分解的時候你可以用_
將不需要的部分省略掉。
let (justTheStatusCode,_) = http404Error
print("The status code is \(justTheStatusCode)")
// 輸出的狀態碼為 404
此外,你還可以用下標來訪問元組中的某個值,元組中的下標從0開始。
print("The status code is \(http404Error.0)")
//輸出的狀態碼為 404
print("The status message is \(http404Error.1)")
// 輸出的狀態描述為 Not Found
你可以在定義元組的時候給單個元組進行命名
let http200Status = (statusCode:200,description:"OK")
當元組中的元素命名后,你可以通過名字來獲取對應的元素的值。
print("The status code is \(http200Status.statusCode)")
////輸出的狀態碼為 200
print("The status message is \(http200Status.description)")
// 輸出的狀態描述為 OK
元組在用作函數返回值的時候顯得尤為重要。已給獲取網頁請求狀態地方函數可以會返回一個(Int,String)
元組來描述網絡請求的狀態。這和只能返回一個值進行比較來說。一個包含兩個不同類型值的元組返回地方信息更有用。了解更多請參考函數與返回值。
注意:
元組在組織臨時值的時候很有用,它們并不適合用到復雜的數據結構里。如果你的數據結構不是臨時的使用,那么請使用類或者結構體而不是用元組。了解更多,請參考類與結構體。
可選類型
當處理值缺失情況的時候,你可以用可選類型來表示。可選類型表明有兩種可能性:或者有值,你可以解析這個可選類型來訪問這個值。或者這個值不存在。
注意:
在C
語言和Objective-C
語言中不存在可選類型這個概念。在Objective-C
語言中最接近的是一個方法中不要么返回一個對象,要么返回nil
,nil
表示缺少一個合法的對象。然而,這只對對象起作用,對于結構體,基本的C數據類型以及枚舉值都不起作用。對于這些類型,Objective-C
方法中一般會返回一個特殊值(比如NSNotFound)來暗示值的缺失。這種方法假設方法的調用者知道并對這些特殊值進行判斷。然而,Swift的可選類型讓你明白任意類型額值缺失,并不需要一個特殊的常量。
這里有一個關于可選值是怎么被用作當做缺省值的例子,Swift中整型中有一個構造器,作用是將一個String
類型的值轉換成一個Int
類型的值。然而,并不是所有的字符串都能夠轉化為整型。字符串123
可以被轉化為整型123
,但是字符串Hello,world
不能被轉化為整型。
下面的例子中使用這種便利構造器將String
轉換成Int
類型。
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
//這里的convertedNumber 被編譯器推斷為'Int'類型,或者類型 ‘optional Int’
因為這個構造器也可能失敗,所以它的返回了一個可選類型的(optional)Int
而不是一個整型。一個可選的Int
類型寫作Int?
而不是Int
。這里的問號包含的值是可選類型,也就是說可能包含Int;類型的值也可能不包含。(不能包含其他的類型,比如說Bool
類型的值或者String類型的值,它只能是Int
類型,或者不存在這個類型。)
nil
你可以給一個不存在的可選類型的變量賦值為nil
:
var serverResponseCode:Int? = 404
// serverResponseCode中包含了一個可選的Int類型的值404
serverResponseCode = nil
// 這里serverResponseCode的值為nil(值不存在)
注意:
你不能夠把nil
用在非可選的變量和常量上。如果你的代碼中存在常量或者變量值缺省的情況,那么在聲明的時候就聲明為可選變量或者常量。
如果你不提供可選類型的變量的初始值,那么變量會自動設置為nil
。
var surveyAnswer:String?
//這里surveyAnswer將會被自動設置為nil
注意:
Swift
中的nil
和Objective-C
中的nil
意義不一樣。在Objective-C
中,nil是一個指向不存在對象的一個指針。在Swift
中,nil
不是一個指針,它是一個確定類型的值。任何類型的可選狀態都可以被設置為nil
,不僅僅是對象類型的數據。
if語句以及強制解析
你可以使用if語句和nil來判斷比較已給可選值是否包含值。你可以使用(==
)以及(!=
)等于以及不等于兩個操作符來判斷一個可選值是否包含值。
如果一個可選類型是有值的,那么它被認為不等于nil
。
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 輸出convertedNumber contains some integer value.
當你確定這個可選值確實包含值的時候,你可以在可選的名字后面加一個!
來獲取值。
這個感嘆號表示我知道這個值有可選值,請使用它。這種被稱為可選值的強制類型解析。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber).")
}
//輸出的是 convertedNumber has an integer value of 123.
更多關于if
條件語句的介紹,請參考控制流。
注意:
使用!
來獲取一個不存在的值可能會導致運行時的錯誤。在進行強制類型解析!
的時候要注意,確保可選類型一定要包含一個不為nil
的值。
可選綁定
使用可選綁定來找出一個可選類型是否包含值,如果包含,如果包含就把值賦給一個臨時變量或者常量。可選綁定可以用在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
包含的一個值,創建一個新的叫做actualNumber
的常量并將包含的值賦給它。
如果轉換成功,那么actualNumber
常量可以在if
語句的第一個分支中使用。它已經被可選類型包含的值初始化過,這里就不需要再變量的后面添加!來進行強制解析獲取它的值了。在這個例子中,actualNumber
只是被用來輸出轉換的結果。
常量和變量都可以使用可選類型綁定。如果你想在if
的第一個分支語句中使用actualNumber
,那么你可以把判斷條件改為if var actualNumber
這里就把可選類型中包含的值就被賦值給一個變量而不是一個常量。
你可以在if
語句中使用多個可選綁定或者Bool
類型來作為條件判斷,它們之前用逗號隔開。如果可選類型中任意一個值為nil
或者Bool
類型中判斷結果為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語句中創建的常量和變量的可選綁定,只能在body中使用。相反,在guard語句中創建的常量和變量的可選綁定,只有在guard語句之外能夠取到值,請參參考提前退出。
隱式解析可選類型
就像上面描述的一樣,可選類型暗示了常量和變量可以沒有值,可選類型通過if語句來判斷是否有值,如果有值的話可以通過可選綁定來進行值的解析。
有時候在程序的結構中,在第一次被賦值以后,可以確定一個可選的類型總是有值。在這種情況下,每次都進行判斷和解析可選值是很低效的,因為可以確定這個值是總是存在的。
這種類型的可選狀態被定義為隱式解析可選類型implicitly unwrapped optionals
。把可選類型(String?
)后面的問號改為(String!
)嘆號來聲明一個隱式解析可選類型。
當可選類型在第一次被賦值以后就可以確定一直有值的時候,隱式解析可選類型顯得很有用。隱式解析可選類型主要被用在Swift中類的構造器中,請參考無主引用以及隱式解析可選屬性。
一個隱式解析可選類型實際上就是一個普通的可選類型,但是可以被當做非可選類型來使用,并不需要每次都是用解析來獲取可選值。下面的例子中展示了可選類型String
和隱式解析可選類型String
行為之間的區別。
let possibleString:String? = "An optional string."
let forcedString:String = possibleString!
//需要用感嘆號來獲取值
let assumedString:String! = "An implicitly unwrapped optional string."
let implicitString:String = assumedString
// 不需要用感嘆號就能獲取值
你可以把隱式解析可選類型當做一個可以自動解析的可選類型。你要做的就是在聲明的時候把感嘆號放到類型的結尾,而不是放到每次取值的可選名字的結尾。
注意:
如果你在隱式可選類型中沒有值的時候嘗試取值,那么會觸發運行時的錯誤。這和你在沒有值的普通可選類型后面添加一個嘆號一樣。
你仍然可以把隱式解析可選類型當做普通的可選類型來判斷它是否包含值:
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
)來應對程序運行過程中可能會出現的錯誤情況。
與可選值不一樣的是,運用值的存在與缺失來表達函數執行的成功與失敗,錯誤處理可以推斷出失敗的原因。并可以傳播到程序的其他地方。
當一個函數遇到出現錯誤條件的時候,它能拋出錯誤。調用函數方法的地方能拋出異常并合理的響應。
func canThrowAnError () throws {
// this function may or may not throw an error
}
一個函數在聲明中添加一個throws
關鍵字是的時候來拋出錯誤消息。當一個函數可能會拋出異常的時候,你應該在表達式中使用前置try
關鍵詞。
Swift
會自動將錯誤傳播到當前的范圍直到它們被catch
句子來處理。
do {
try canThrowAnError()
// no error was thrown
} catch {
// an error was thrown
}
一個do
語句創建了一個新的作用域,它允許錯誤被傳遞到一個或者多個catch
語句中。
這是一個如何運用錯誤處理來應對不同錯誤情況的例子:
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleardishes {
washDishes()
} catch SandwichError.missingTngredients(let ingredients) {
buyGroceries(ingredients)
}
在這個例子中,makeASandwich()
函數會拋出一個錯誤消息條件是如果沒有干凈的盤子或者某個原料的缺失。由于makeASandwich()
可能會拋出錯誤,所以函數調用被包在了try
語句中。將函數包在一個do
語句中,那么任何被拋出的錯誤都會被傳播到catch
語句中。
如果沒有錯誤被拋出,那么eatASandwich()
函數會被調用。如果一個匹配SandwichError.outOfCleardishes
的錯誤被拋出,那么會執行washDishes()
這個函數,如果異常SandwichError.missingTngredients
被捕獲到,那么buyGroceries(_:)
函數將會被調用,并且會使用catch
所捕獲到的關聯值String
作為參數。
拋出,捕獲以及傳播錯誤等,會在錯誤處理的章節中說明。
斷言和先決條件
斷言和先決條件是在運行時進行檢查的。你可以用它們來檢查在執行后面代碼的之前是否一個必要的條件已經滿足了。如果斷言或者先決條件中的bool
值是true
的時候,那么代碼會像往常一樣執行。如果條件判斷為false
,當前程序的狀態是無效的,代碼的執行會結束,你的app會被終止。
你使用斷言和先決條件來表達你做的假設和你在編碼時候的希望執行的方式。你可以將這些包含在代碼中。斷言幫助你在開發階段找到錯誤和不正確的假設,先決條件幫助你在在生產環境中發現存在的問題。
除了在運行時驗證你的期望值,斷言和先決條件也變成了你代碼中一種有用的文檔形式。和上面討論的錯誤處理不同,斷言和先決條件不是用來處理可以恢復或者可以預期的錯誤。因為一個斷言失敗表明了程序正在處于一個無效的狀態,沒有辦法來捕獲一個失敗的斷言。
使用斷言和先決條件不是一個能夠避免出現程序無效狀態的編碼方法。然而,如果一個無效狀態的程序產生快了。斷言可以強制檢查你的數據和程序狀態,使程序按照預測中的被終止,并幫助我們更簡單的對這個問題進行調試。一旦遇到無效的狀態,程序就會被終止,防止無效的狀態對程序造成進一步的傷害。
斷言和先決條件的不同之處在于它們什么時候進行檢測。斷言僅僅在調試的時候運行,但是先決條件不僅僅在調試的時候能運行在生產環境下也能運行。在生產環境下,斷言條件不會被評估。這意味著你可以在開發階段多使用斷言,這些斷言在生產條件下不會造成影響。
使用斷言進行調試
你可以用Swift標準庫的函數assert(_:_:file:line:)
來編寫一個斷言。
你向這個函數傳入一個判斷結果為true和false的表達式以及一條錯誤情況下展示的信息。例如:
let age = -3
assert(age >= 0,"A person's age can't be less than zero.")
// 這個斷言會失敗 因為一個人的年齡不可能小于0(把斷言中的語句變為age <= 0,就不會走這個斷言)
在這個例子中,只有age >= 0
為true
也就是說age
的值為非負數的時候,代碼才會繼續執行。如果age的值為負數,就像上面代碼中的一樣,那么,age >= 0
為false
,斷言失敗,使得應用被終止運行。
你也可以省略掉斷言的提示信息,例如:當斷言條件可能會重復執行的時候
assert(age >= 0)
如果代碼已經被檢查過,你可以使用函數assertionFailure(_:file:line:)
來表明斷言失敗了。例如:
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.")
}
強制執行先決條件
當一個條件可能為假,但是繼續執行下去要求條件必須為真的時候,需要使用先決條件。例如:使用先決條件來判斷下標有沒有越界,或者來檢查是否將一個正確的參數傳給了函數。
你可以使用函數precondition(_:_:file:line:)
來寫一個先決條件。向這個函數傳入一個結構為true或者false的表達式以及一條錯誤條件下顯示信息。例如:
// 下標的實現
precondition(index > 0, "Index must be greater than zero.")
你可以調用函數preconditionFailure(_:file:line:)來表明出現了一個錯誤,例如:switch
的進入了default
分支,所有的有效輸入數據都應該被其他分支所處理而不是默認的default
分支。
注意:
如果你使用不檢查的模式(-Ounchecked)進行編譯,先決條件將不會進行檢查。編譯器會假設所有的先決條件都是true
,這將優化你的代碼。然而致命的錯誤函數(_:file:line:)
總是中斷執行,無論你進行什么樣的優化設置。
在設計原型和早期的開發階段你可以使用致命的錯誤函數(_:file:line:)
,這個階段只是對方法的聲明,
但是沒有具體的實現。你可以在方法fatalError("Unimplemented")
進行具體實現。因為fatalError
不會像斷言和先決條件那樣可以被優化,所以你可以確保當代碼執行到一個沒有實現的方法是的時候,程序會被中斷。
更多swift4.1翻譯請查看github。
熱愛生活,分享快樂。好記性不如爛筆頭。多寫,多記,多實踐,多思考。