簡介
從上面一章中,你已經瞭解了Swift中的變量,常量,元組,字符以及控制流。這章內容讓你進一步瞭解Swift的語法特點。
這章節重心仍然放在基礎語法上,但會有些更高級的內容。就像上一章你做的那樣,在playground中一邊敲代碼,一邊直接在右邊查看結果。
這章結束後,Swift的基本內容就被你記得七七八八了吧。然後我們就可以在真實的應用中開始練習了。所以,有沒有覺得playground拿來練習Swift的基礎語法溜的飛起。(真心無力,天哪!!!這麼多廢話,後面還有兩百頁,~~(>_<)~~)
奔跑吧!騷年,繼續Swift吧!
Optionals - 可選類型
你只要編過程序,就一定知道關於null的指針是有多惱火。當你準備賦值或者用一個對象調用方法時,對象還沒初始化,不用說會發生什麼!內心中萬匹草泥馬奔騰而過!在java中,對於null指針會拋出null指針異常讓你處理。
在Object-C運行時,數據傳遞的是nil則返回的也是nil。這貌似說明null指針也是安全的。然而這並沒有什麼卵用,千萬別指望他的運行情況和非null時是一樣的。所以我們通常的做法是添加assertion斷言以確保所有的變量都不是nil。在調試模式中,當碰到nil時應用程序會崩潰,所以你可以在發佈你的代碼前用這個方法找到並修復你有nil的地方。
在Swift中,處理是不一樣的。
打開xcode,選擇File\New\Playground…, 起名叫Chapter_2並選擇iOS platform ,然後點擊next將文件保存在本地。
嘗試演練下吧:
var str = "Hello, playground"
print(str)
正如你看到的,第一行創建了一個叫str的新的變量,並且初始化爲”Hello, playground” 。但是如果在這個時候沒有初始化內容呢?其他語言中,會將nil,null或者一個初始值賦值給沒有值的變量。
刪除第一行str後面的所有內容,你可以看到提示報錯:Type annotation missing in pattern (模板丟失類型說明)
這很明顯的可以看出編譯器不知道str應該是什麼類型的數據,畢竟你毛都不給別個一根,你讓別個怎麼推斷類型。
如果他需要一個明確的值來進行推斷數據的類型的話,我們可以試試直接指定變量的類型看看:
var str: String
(這次會提示一個不同的報錯:Variable ‘str’ used before being initialized. (變量str沒有初始化)
閃瞎氪金狗眼!Swift在編譯的時候就不允許你使用未初始化的變量。這意味着你再也不會出現意外的nil值。)
事實證明在這行代碼後並沒有這個報錯,也就是說Swift2.0或3.0去掉了,只有當你調用這個str變量的時候纔會報這個錯。即在使用print(str)提示此錯誤。
但是如果你就是想要一個nil值的變量呢?(些許蛋疼),有時,你可能就是需要一個nil值的變量,比如用nil來表示返回的是一個error錯誤或者是沒有值的情況。
或許你覺得,如果你需要nil的變量,只需賦值成nil就ok了:
var str: String = nil
但是,但是,又報錯了。這次編譯器提示:Type ‘string’ does not conform to protocol ‘NilLiteralConvertible’. (string 不符合 NilLiteralConvertible協議)。這是Swift的另一個安全機制起作用了,這是因爲你聲明的變量是String,那他就一定要是String,nil不是String。
廢話這麼多,終於來正題了,你想用nil的變量,這個時候你就需要用到optional(可選類型)了.在這個語言中可選類型的廣義概念是說包含有有值和可能有值的兩種情況。在其它語言中你可能經常看到,我們用-1或0這樣的值來表示沒有值。null指針雖然是null,但他也是一種明確的值類型,這樣你大概能猜出上面爲什麼會提示報錯了吧,因爲null指針也是一種對象類型,只是不會指向任何一個有效的對象而已。
Declaring optionals - 可選類型聲明
先看下怎麼用可選類型吧
var str: String?
你只需要添加一個問號到類型聲明的後面,標明這個String 是個可選類型就可以了。因爲這個str變量是可選類型的,所以他既可以有nil又可以有一個String實例。str會被初始化爲nil因爲你還沒有給這個String指明一個明確的值。
接着改變代碼如下:
var str: String? = "Hello Swift by Tutorials!"
如你所料控制檯輸出:”Hello Swift by Tutorials!” ,看起來就像是等號右邊的值直接賦值給了左邊的String可選類型中的有值部分。實在是很神奇,在分配的時候,Swift自動分配了可選類型中值是賦值給nil那部分還是給有值的那部分。
當你輸出這個變量時,注意playground的右邊顯示:
直接說明了括號裏的字符串是可選類型的。
雖然看起來像是普通的string值,但一樣嗎?,做個測試
str = str.uppercaseString
報錯提示:error: value of optional type ‘String?’ not unwrapped; did you mean to use ‘!’ or ‘?’?
提示你還沒有解包,這個內容後面再講,以前版本會提示error: ‘String?’ does not have a member named ‘uppercaseString’. 明顯在Swift3.0上提示又修改了
可以簡單的理解雖然String?和String看上去很相似,有些方法用着也一樣,不過這是兩種不同的數據類型,String?裏面沒有字符串String中的方法。
Swift提供了一種安全的方式讓你可以使用可選類型中包含的值,就是用if判斷語句來重新賦值給一個新的變量,讓新的變量來調用
if let unwrappedStr = str {
print("Unwrapped! \(unwrappedStr.uppercaseString)")
}
用if語言塊,可以直接將str解開,並將值賦值給一個叫unwrappedStr的常量,如果str解開包含了一個實例字符串,那麼unwrappedStr就會變成String類型的常量並讓if語句通過。
修改代碼,刪除str的初始化語句,並修改if語句添加如下
} else {
print("Was nil")
}
因爲這個時候str解開是一個nil值,所以這個if語句無法通過。這說明如果你想使用可選類型中的值,Swift提供了一種非常安全的方式讓你調用。另外順帶一提,如果賦值let unwrappedStr = str,則unwrappedStr是個可選類型,也就是說只有在剛剛的if語句中,解開賦值後unwrappedStr會變成單純的String類型,而不是可選類型。
Forced unwrapping - 強解
如果你確定可選類型中有值,那你可以使用一種叫強制解析的手段直接使用可選類型中的值,也就是說你可以不通過if語句塊來檢查可選類型中是否包含有值。
修改代碼:
var str: String? = "Hello Swift by Tutorials!"
print("Force unwrapped! \(str!.uppercaseString)")
在變量名後面添加一個感嘆號便是強解,無論str中是否有值你都可以直接使用uppercaseString。在當前示例下,因爲str中有個實際的字符串實例,所以一切運行正常,修改代碼,刪除str的初始化賦值,會報錯:unexpectedly found nil while unwrapping an Optional value.
這是個運行時錯誤,會導致應用崩潰。這個崩潰是因爲你在強制解析一個只有nil值的可選變量後並進行了操作。
小技巧:雖然強解非常的有用,但是他破壞了Swift在可選類型中提供的安全機制。只有在你絕對需要以及百分百肯定這個可選類型不可能爲nil時再用這個代碼執行。
Implicit unwrapping - 隱式解包
你也可以在不使用手動解包或者強制解包的情況下使用可選類型的變量或常量。修改代碼:
var str: String! = "Hello Swift by Tutorials!"
客官可曾發現有何不同?仔細看類型聲明的後面,現在用一個感嘆號替代了以前的問號,用來表示想要隱式的解開這個變量並使用。
現在你就可以用這個隱式解包的類型如其他你所知道的類型一樣正常使用了,for example:
str = str.lowercaseString
print(str)
他看上去完全不像是一個可選類型了。再來試試刪除掉賦值內容,就會發現報和剛纔強制解包時一樣的錯誤:unexpectedly found nil while unwrapping an Optional value
你可能現在很難看到有用隱式解包展開的。但他們這種類型的本身特點以及可以用來處理的哪些問題,便已經決定了隱式解包也是有用的。
你可以用if語句塊來檢查可選類型。將最後兩行代碼用if語句塊包裹起來,如下:
if str != nil {
str = str.lowercaseString
print(str)
}
現在代碼可以正常運行了,像上面這種檢查可選類型是不是nil在Object-C中你可能也是這樣用的。在Object-C中,你通過是否是nil來判斷是否是錯false。在Swift中,稍有不同。nil返回的則真正是“沒有值”這種狀態類型。
小技巧:你用隱式解包也要像強制解包一樣謹慎小心,除了在他的聲明的時候,隱式解包類型的變量實在是和平常的標準的變量太像了,所以用的時候一定要小心小心在小心。
Optional chaining - 可選鏈
Optional chaining(可選鏈)是一種使用可選類型的簡潔方式,不用每次使用都使用if/else 條件語句塊。
如果你做過Object-C開發,那你肯定對delegate的代理模式很熟悉。一個對象的代理負責調用另外一個對象的方法。常見的委託代理實現是可選的,如果代理的方法沒有設置,則代理的對象不會去調用他的代理方法。這種實現原理對可選類型來說就非常的合適。
我們不想你馬上就去了解類和代理內容(我們在第三章“類和結構體”以及第八章“Swift和Cocoa”進行了解)。所以我們先用一個簡單的String來進行示範,雖然簡單,但是原理是一樣的。
更新代碼:
var maybeString: String? = "Hello Swift by Tutorials!”
let uppercase = maybeString?.uppercaseString
第一行我們聲明瞭一個叫maybeString的String可選類型變量,在第二行,我們用一個問號添加在變量名後面形成一個可選鏈。運行代碼時,他會檢查可選類型maybeString的內容,如果內容是一個實例,則繼續執行uppercaseString 方法。如果是nil,則返回nil。
這樣uppercase操作也變成可選的了,因爲他要操作的對象可能是maybeString也可能是nil了。這和Object-C中操作nil信息很像。操作nil則返回nil。
Collections - 集合
所有的語言都需要有像arrays數組,dictionaries字典以及set集的集合。在Object-C中Foundation framework 提供了很多不同類型的集合,最常用的是NSArray和NSDictionary 。在蘋果中這都變成了非常精簡好用的語法,所以怎麼可能不在Swift中也嘗試下呢?
事實證明,在Swift中只提供了兩種最原始的集合類型:數組和字典,在Swift的庫中有提供。他們的使用方式與其他語言還是有丁點不一樣。
Arrays - 數組
如你所料的,在Swift中數組是一個有序的元素集合,在playground中添加如下代碼:
var array = [1, 2, 3, 4, 5]
生成了一個含有5個元素的數組
你可以使用下標來訪問數組的元素。
print(array[2])
Swift的下標也是從0開始的,所以輸出第三個元素。
你可以用append() 來增加數組的內容,如下:
array.append(6)
print(array)
現在你的數組裏面有6個元素了。
你甚至可以通過一個序列的內容直接添加到數組中,如範圍類型range。如下所示:
array.extend(7...10) //被廢棄,現在用下面的方法
array.appendContentsOf(7...10),//現在你就有10個元素了
挑戰:找出從數組中刪除元素的方法!
現在來試試數組中添加不同類型的元素。
array.append("11")
編譯器報錯。如果你是從Object-C轉過來的,你肯定會有些蛋蛋的憂傷,在Object-C中,數組可以包含有任何類型的對象,但在Swift中數組是強約束類型,裏面只能有一種明確的數據類型。上面的例子是數組從元素中推斷出當前array是一個integer的數組,因爲你在初始化時便在數組中裝的全是integer。
修改初始化代碼:var array: [Int] = [1, 2, 3, 4, 5]
上面的聲明演示了在Swift中如何給一個數組的元素定義類型:簡單的方式就是直接添加一個[],你也可以用一個較長的泛型語法,Array(一個有着Int類型約束的數組)。明顯的前面的方式比較簡單,我們在第四章“Generics 泛型”中再去了解這種語法吧。
你可以和Object-C中NSArray那樣生成一個數組,只是要指定一個數組中元素的類型。你應該創建一個特定類型的數組,這樣會更安全些。有沒有覺得Swift的安全機制到處都是。
挑戰:創建不同類型的的數組進行練習,比如字符串之類的。
Dictionaries - 字典
數組提供了一個序列的元素列表,字典提供了一個有鍵值對映射的列表。
代碼演示:
var dictionary = [1: "Dog", 2: "Cat"]
生成了一個有着兩個鍵值對的字典。語法和數組很像,不過使用逗號將每個對象列表分開,每個鍵值對以冒號分隔開。Object-C的開發者可能會發現字典的語法有點不一樣,用的依然是中括號而不是花括號。
和數組一樣,字典也是強約束類型。在這個例子中,這是一個Int類型的key,String類型的值的Dictionary
print(dictionary[1])
輸出“Dog”
你也可以通過key來修改字典。代碼示例:
dictionary[3] = "Mouse"
print(dictionary)
現在字典中有個三個值,如果你對Object-C或其它編程語言熟悉,想必對這個語法也不會感到陌生。變量後面的方括號表示字典的key。在這個示例中,因爲值被分配到了一個叫3的key中。
挑戰:修改key爲2的內容爲“Elephant” 。
也可以用這種方法移除對應的對象:
dictionary[3] = nil
這示範的是給key叫3的值設置爲nil,所以就相當於是直接移除了。
也可以直接用key來獲取key對應的值。試試下面的:
print(dictionary[1])
你可能已經發現playground上顯示的是”Optional(“Dog”)} ,這意味着他返回的內容是一個可選類型。這完全說的通,當你訪問一個並不存在的key時,可不就要返回nil嘛。
和上面提到的解包一起使用下:
if let value = dictionary[1] {
print("Value is \(value)")
}
這又很好的展示了Swift的默認的安全機制,迫使你時刻考慮着值爲nil的情況,畢竟大多數情況下你都是需要值有內容的。
References and copies - 引用和複製
數組和字典都在Swift中展示了引用類型和值類型的操作。編程語言在運行時都有他們自己對應的處理方式,所以明白Swift是怎麼工作相當重要。
首先,我們看一下字典。就像你看到的,字典和數組十分相似。代碼如下:
var dictionaryA = [1: 1, 2: 4, 3: 9, 4: 16]
var dictionaryB = dictionaryA
print(dictionaryA)
print(dictionaryB)
這樣做你期待什麼?明顯是兩個一模一樣的字典不是。
再來添加一行代碼修改下:
dictionaryB[4] = nil
print(dictionaryA)
print(dictionaryB)
輸出顯示b字典比a字典的內容少。同樣的,如果你修改字典的內容而不是刪除,你會發現,你修改的也只是你修改的那個字典,另外一個字典並不會改變。
這是一個非常重要的注意點:當你分配一個新的變量或常量字典,或者是將字典以參數形式傳遞給方法,字典都是複製一份新的內容。
讓我們在數組上做一點相同的事,更新代碼如下:
var arrayA = [1, 2, 3, 4, 5]
var arrayB = arrayA
print(arrayA)
print(arrayB)
arrayB.removeAtIndex(0)
print(arrayA)
print(arrayB)
結果自然也不會出人意料纔對,當你修改或刪除一個數組時,並不會修改另一個數據
現在來試試修改裏面的內容
arrayB[0] = 10
不出所料,也只有數組b內容被修改了,這意味着在分配的時候是複製了一份新的內容。
和其他語言中如Object-C的引用對比。當一個NSArray的指針被分配到另一個變量時,他們的指針指向的是同一個數組實例。修改一個數組,另外一個數組也會改變。
Constant collections - 常量集合
正如你第一章看到的,Swift有常量和變量兩種。到目前爲止,我們都只使用了用var聲明的數組和字典。你也可以用let 來定義數組和字典,這種聲明方式可能和你想的有點出入。
代碼君:
let constantArray = [1, 2, 3, 4, 5]
除了是常量,聲明和前面的聲明是一樣的。常量數組對元素無法進行添加和刪除功能。
讓我們look look~~
constantArray.append(6)
constantArray.removeAtIndex(0)
提示錯誤:let類型的不可修改。
這是Swift的再次安全控制,讓你不會意外性的修改了數組內容。常量字典和常量數組一樣。
挑戰:創建一個常量字典,嘗試下刪除等不允許改變的操作。
接下來乾點什麼呢?
從上面兩章內容過後,你已經瞭解了Swift的的基本變量,常量以及可選類型,集合等。
那現在做what? 是時候把現在知道的內容運用到實際的應用中了,我們下一章便開始。
抓緊時間該休息的休息,該上廁所的上廁所。新的挑戰開始了。