一個下午讓你掌握Swift基礎 ( 9/9 ) 可選值

簡介

這是一個Swift語言教程,基于最新的iOS 9,Xcode 7.3和Swift 2.2,會為你介紹Swift編程非常基礎的內容。從電腦如何工作的全程基本原理到語言結構,你會足夠了解這門語言,來處理數據和管理代碼的行為。

快速鏈接


可選值

你目前遇到的所有變量和常量都有實際的值。當你有一個字符串變量的時候,比如 var name,它有一個相關的字符串值,例如“張嘉夫”。它可能是一個空字符串,像 “”,但盡管如此,還是有一個可以訪問到的值。

那是 Swift 自帶的安全特色之一:如果說類型是 Int 或 String 那么就有一個實際的整數或字符串在那里,有保證的。

這篇文章會為你介紹可選值(optionals)的概念,一個特殊的 Swift 類型,不只可以表示一個值,也可以沒有值。在這篇文章的最后,你會知道為什么需要可選值,以及如何安全的使用它們。

介紹 nil

有時候,有一個沒有值的值是很有用的。想象一個場景,你需要訪問一個人的認證信息。你想要存儲這個人的名字、年齡和職業。名字和年齡都是必須有值的東西—每個人都有。但不是每個人都有工作,所以職業這個值的缺失就是你需要處理的事情。

讓我們實際看看這個情況。

在不知道可選值的情況下,這是你可能去表示一個人的名字、年齡和職業的方式:

var name: String = "張嘉夫"
var age: Int = 23
var occupation: String = "軟件開發者 & 作家"

但如果我被解雇了呢?也許我中了彩票然后再也不想工作了(我真的想!)。這時候能夠訪問一個不存在的值就會很有用了。

為什么你不能就用一個空的字符串呢?好吧,你可以!讓我們討論一下那樣會怎么做,以及為什么可選值是一個更好的選項。

標記值

一個有效值,用來表示不存在的,叫做標記值(sentinel value)。這是前一個例子里你的空字符串就會是這樣。

讓我們看一下另一個例子。假設你寫了一些代碼,從服務器上請求什么東西,那么你可以用一個變量來存儲服務器返回的任意錯誤代碼:

var errorCode: Int = 0

在成功的時候,你可以用零來表示沒有錯誤。意味著 0 是標記值。

就像職業的空字符串,這可以奏效,但對于程序員有潛在的混淆的風險。0 實際上可能是一個有效的錯誤代碼—也或許在未來會是。無論怎么樣,你不能完全確定這是沒有錯誤的。

這兩種情況下,如果有一個特殊類型可以表示值的缺失就會更好。這樣什么時候有值什么時候沒有就很明確了。
Nil 表示值的缺失,你會看到 Swift 如何用相當優雅的方式來直接把這個概念并入語言中。

其他有一些編程語言只用標記值。有一些,比如 Objective-C,有 ni 的概念,但僅僅是零的同義詞。只是另一個標記值罷了。

Swift 介紹了一個全新的類型,叫做可選值來處理一個值可能是或不是 nil。這意味著如果你在處理一個非可選值類型,那么它就被保證了一定有值,不需要去擔心有沒有值??偸怯械摹O嗨频?,如果你在用一個可選值類型,那你就必須要考慮 nil 情況了。它移除了使用標記值帶來的歧義。

介紹可選值

可選值是 Swift 對于同時表示值和值的缺失問題的解決方案。一個可選值類型可以引用一個值或是 nil。

把可選值想象為一個盒子:它要么包含一個值,要么沒有。當它沒有包含值的時候,它被認為包含了 nil。盒子本身總是存在;它一直在這里,你可以打開然后看看里面。

另一方面,字符串或整數,周圍并沒有這個盒子。相關那里總有一個值,比如 “hello” 或 42。記住,非可選值類型確保有一個實際值。

**注意:**學過物理的這時候可能會想起薛定諤的貓??蛇x值和它有一點像,除了這不是一個生死攸關的問題!

用下面的語法聲明可選值類型:

var errorCode: Int?

它和標準聲明之間的唯一區別就是類型末尾的問號。這個例子里,errorCode 是“ Int 可選值”。這意味著變量本身就像一個盒子,包含一個 Int 或 nil。

設置值很簡單。你可以設置它為一個 Int,像這樣:

errorCode = 100

或者你可以設置它為 ni,像這樣:

errorCode = nil

圖表可以幫助你形象化理解正在發生什么:

可選值盒子永遠存在。當你給變量賦值 100 的時候,你在用值來填滿盒子。當你給變量賦值 nil 的時候,你在清空盒子。

花幾分鐘想一下這個概念。用盒子的類比會是一個巨大的幫助,在你讀完這篇文章剩下的部分的時候,然后開始使用可選值。

迷你練習

創建一個叫做 myFavoriteSong 的 String 可選值。如果你有一首最愛的歌,就把它設置為表示那首歌。如果你有不止一首或者沒有最愛的,就設置可選值為 nil。

拆包可選值

有可選值一切都很好,但你應該會好奇怎么看到盒子里面,操作它所包含的值。

讓我們看看打印可選值的值得時候會發生什么:

let ageInteger: Int? = 23
print(ageInteger)

如下輸出:

Optional(30)

那不是你真正想要的—盡管如果仔細想一想,它還是有意義的。你的代碼輸出了盒子。結果說:“ ageInteger 是一個可選值,包含值 30”。

要看這個值類型如何區別于非可選值類型,那就讓我們看看如果嘗試把 ageInteger 當一個正常整數來使用會怎么樣:

print(ageInteger + 1)

代碼觸發了一個錯誤:

error: value of optional type 'Int?' not unwrapped; did you mean to use'!' or '?'?(錯誤:可選值類型 'Int?' 的值沒有被拆包;你是不是想使用 '!' 或 '?'?)

不正確是因為你嘗試把一個整數加到一個盒子上—不是盒子里面的值,而是盒子本身。這是沒有意義的!

強制拆包

錯誤信息給了解決方案的指示:它告訴你可選值“沒有被拆包”。你需要從盒子里把值拆出來。就像過圣誕節!
來看看怎么做??紤]如下聲明:

var authorName: String? = "張嘉夫"

你可以用兩種不同的方法來拆包可選值。第一種叫做強制拆包(force unwrapping),像這樣做:

var unwrappedAuthorName = authorName!
print("作者是\(unwrappedAuthor)")

變量名后面的感嘆號告訴編譯器你想看到盒子里面,并且把值拿出來。這是結果:

作者是張嘉夫

很棒!這就是你想要的。

使用詞語“強制”和感嘆號 ! 應該給你傳達了一種危險感覺,這值得注意。你應該盡量少的使用強制拆包。要知道為什么,考慮一下如果可選值不包含值得時候會發生什么:

authorName = nil
var unwrappedAuthorName = authorName!
print("作者是\(unwrappedAuthorName)")

這個代碼產生了如下運行時錯誤:

fatal error: unexpectedly found nil while unwrapping an Optional value(致命錯誤:未料到地,拆包一個可選值的時候發現是 nil)

發生了這個錯誤是因為在你嘗試拆包變量的時候它沒有包含任何值。糟糕的是你是在運行時得到這個錯誤,而不是編譯時,意味著只有在用非法的輸入執行這部分代碼的時候才會發生,你才能注意到它。更糟糕的是,如果這個代碼在一個 app 內部,運行時錯誤會導致 app 崩潰!

怎么才能讓它安全呢?

要在這里停止運行時錯誤,你可以包住代碼,然后用一個檢查來拆包可選值,像這樣:

if authorName != nil {
    var unwrappedAuthorName = authorName!
    print("作者是\(unwrappedAuthorName)")
} else {
    print("沒有作者。")
}

if 語句檢查可選值是不是包含 nil。如果不是,意味著它包含一個你可以拆包的值。

這個代碼現在安全了,但仍不失最佳狀態。如果你依賴這個技術,每次你想拆包可選值的時候都要記住檢查 nil。這會開始變得單調乏味,有一天你會忘記,然后再次導致運行時錯誤的可能性。

然后回到繪圖板!

If let 綁定

幸運的是,Swift 包含了一個叫做 if let 綁定(binding)的特色,讓你可以安全的訪問可選值里面的值。像這樣使用:

if let unwrappedAuthorName: String = authorName {
    print("作者是\(unwrappedAuthorName)")
} else {
    print("沒有作者。")
}

你會立馬注意到這次沒有感嘆號了,并且 unwrappedAuthorName 的類型是一個普通的 String,不是一個可選值 String。

**注意:**一般的不會在 if let 語句指定拆包變量的類型,這里是為了顯示清楚。

這個特殊語法擺脫了可選值類型。如果可選值包含值,代碼執行 if 語句的 if 那邊,自動拆包 unwrappedAuthorName 變量,因為你知道這是安全的,可以這么做。

如果可選值不包含值,代碼執行 if 語句的 else 那邊。這個例子里,unwrappedAuthorName 甚至都不存在。
你可以看到 if let 綁定與強制拆包相比有多么安全,你應該任何可能的時候選擇它。

你甚至可以一次拆包多個值,像這樣:

let authorName: String? = "張嘉夫"
let authorAge: Int? = 23
if let name: String = authorName,
    age: Int = authorAge {
    print("作者是\(age)歲的\(name)。")
} else {
    print("沒有作者或沒有年齡。")
}

這段代碼拆包了兩個值。它只在兩個可選值都包含值得時候才會執行 if 部分的語句。
現在你知道如何安全的看進一個可選值里面,并且取出值,如果有的話。

迷你練習

  1. 使用之前的 myFavoriteSong 變量,使用 if let 綁定來檢查是否包含值。如果是,print 這個值。如果沒有,print “我沒有一首喜歡的音樂。”
  2. 把 myFavoriteSong 設置為當前的相反面;也就是說,如果是 nil,就設置為一個字符串,如果是一個字符串,就設置為 nil。觀察你 print 的結果會有什么變化。

Nil 合并

有一個終極和更常用的方式來拆包可選值。當你想要取出可選值里的值,不論值是什么,就用它—如果是 nil,就會使用一個默認值。這叫做 nil 合并(coalescing)

這是它工作的方式:

var optionalInt: Int? = 10
var result: Int = optionalInt ?? 0

nil 合并發生在第二行,帶有兩個問號(??)。這行表示 result 會等于 optionalInt 里的值,或者如果 optionalInt 包含的是 nil 就等于 0。

所以這個例子里,result 包含具體的 Int 值 10。

上面的代碼和下面等同:

var optionalInt: Int? = 10
var result: Int
if let unwrapped = optionalInt {
    result = unwrapped
} else {
    result = 0
}

設置 optionalInt 為 nil,像這樣:

optionalInt = nil
var result: Int = optionalInt ?? 0

現在你的 result 等于 0。

關鍵點

  • Nil 表示值的缺失。
  • 非可選值變量和常量一定會有一個非 nil 值。
  • 可選值(Optional)變量和常量就像盒子,可以包含一個值或是空的(nil)。
  • 要處理可選值里的值,首先必須從可選值里拆包。
  • 拆包可選值最安全的方式是使用 if let 綁定(binding)nil 合并。避免強制拆包(forced unwrapping),它可能會導致一個運行時錯誤。

接下來去哪兒?

這就是可選值啦,Swift 的一個核心特色,幫助讓語言更安全,使用起來更簡單。你會發現代碼里會貫穿始終使用使用它們。他們幫你讓代碼更安全,通過保證明確處理值的缺失情況。在你的 Swift 體驗中,你應該會越來越欣賞它。

特別是,你會在 10-12 篇教程中使用它們,并且在那里學習集合(collections)。

挑戰

你已經學習了可選值背后的理論,并且在實踐中見過他們了。現在輪到你搞一下了!

挑戰 A:你就是編譯器

下列哪個是有效語句?

var name: String? = "嘉夫"
var age: Int = nil
let distance: Float = 26.7
var middleName: String? = nil

挑戰 B:分而治之

首先,創建一個函數,返回一個整數可以被另一個整數整數的次數。如果不能整除就返回 nil。命名函數為 divideIfWhole。

然后,寫代碼嘗試拆包函數的可選值結果。應該有兩種情況:如果成功,print “Yep, 它整除 (answer) 次”,如果失敗,print “除不盡 :[”。

最后,測試你的函數:

  1. 10 除以 2。應該 print “Yep,它整除 5 次。”
  2. 10 除以 3。應該 print “除不盡 :[”

提示 1:使用如下作為函數構造的開始:

func divideIfWhole(value: Int, by divisor: Int)

你需要添加返回類型,這應該是一個可選值!

提示 2:你可以使用模除操作符(%)來決定一個值是否能被另一個除盡;回想一下,這個操作返回兩個數字相除的余數。例如,10 % 2 = 0 意味著 10 可以被 0 除盡,沒有余數,但是 10 % 3 = 1 意味著 10 被 3 除了三次,有一個余數為 1。

挑戰 C:重構與改善

上一個挑戰里,你用 if 語句寫的代碼。在這個挑戰里,重構代碼,使用 nil 合并。這次,所有情況都讓它輸出 “它除了 X 次”,但如果不是整除,那 X 就應該是 0。

挑戰和迷你練習源代碼

https://yunpan.cn/cBDRwNvgpHy4G (提取碼:f961)


介紹

歡迎來到Swift世界!Swift是一門蘋果在2014年夏天發布的編程語言。從那之后,Swift發布了一個主要的版本跳躍,成為了開始在蘋果平臺:iOS,OS X,watchOS和tvOS開發的最簡單的方式。

誰適合這篇教程

這篇教程適合懂一點編程、并且希望學習Swift的人。也許你已經為網站寫過一些JavaScript代碼,或者用Python寫過一些簡短的程序。這篇教程就是為你準備的!你會學習到編程的基本概念,同時也會成為Swift語言小能手。

如果你是赤裸裸的編程新手,這篇教程也是為你準備的!教程里貫穿有簡短的鍛煉和挑戰來給你一些編程練習,同時測試你的知識。

需要準備什么

要看這篇教程,你需要準備如下的東西:

  • 一臺運行OS X El Captian(10.11)的Mac,帶有最新發布的更新并且安裝了安全補丁。這樣你才能夠安裝需要的開發工具:最新版本的Xcode。
  • Xcode 7.3 或更新的版本。Xcode是用Swift寫代碼的主要開發工具。最小也需要Xcode 7.3版本,因為那個版本包含Swift 2.2。你可以免費從Mac App Store下載Xcode的最新版本,這里:http://apple.co/1FLn51R

如果你還沒有安裝Xcode最新版本,在繼續看下面的教程前要確定安裝。

如何使用這篇教程

每篇教程都會介紹觸手可及的話題理論,伴隨大量Swift代碼來示范在學習的實際的應用程序。

教程里的所有代碼都是平臺中立的;這意味著不是為iOS、OS X或任何其它平臺而特定。代碼在playgrounds里運行,你在本篇中已經學習了。

在剩下的教程里,你可以把代碼在自己的playground里輸入進去。這樣你就可以和代碼“玩耍”(play around),做一些改變立即就能看見代碼運行的結果。

剩下的教程里會貫穿實際小練習,都是簡短的練習,關于觸手可及的主題。每篇的末尾也有挑戰,會有編程問題也會有長一點的代碼練習來測試你的知識。做完就能掌握大部分的Swift基礎知識。

教程更新

教程會隨Swift語言的更新而更新,會發布在我的簡書和知乎專欄上,搜索“張嘉夫”即可關注我。

目錄

本教程從一些基礎工作開始來讓你起步:

  • 第1篇,編程本質 & Playground基礎 - 這就是,到編程世界的入門介紹!你會從電腦和編程的預覽開始,然后剩余時間給Swift playground打個招呼。
  • 第2篇,變量 & 常量 - 你會學習到變量和常量,這是用來存儲數據的“地方”。你也會學習數據類型以及Swift如何追蹤數據類型并在代碼中進行傳輸。
  • 第3篇,數字類型 & 操作 - 你會從基礎的數字類型比如整形和浮點型數字開始,當然也包括布爾類型。也會看到一些在實際操作,從比較到算數操作如加減。
  • 第4篇,字符串 - 用字符串來存儲文字-從按鈕里的文字到圖片的標注到這篇教程全部的文字,存儲這所有的一切!你會學習到string和character類型,以及基于這些類型的一些常見操作。

在腦海中有基礎數據類型后,就該用那些數據做一些事情了:

  • 第5篇,做判斷 - 代碼不總是直接從頭運行到尾。你會學習在代碼里如何做判決并且設定情況來運行某段代碼。
  • 第6篇,重復步驟 - 繼續不要讓代碼直線運行的主題,你會學習到如何使用循環來重復某些步驟。
  • 第7篇,函數 - 函數是Swift中用來構建代碼的基礎建筑。你會學習到如何定義函數來分組代碼到可復用單元中。
  • 第8篇,閉包(Closures) - 閉包和函數很接近。你會學習到如何使用它們來輕松傳遞代碼塊。

最后一篇會回到數據:

  • 第9節,可選值 - 這篇講可選值,Swift中的一種特殊類型,表示既有可能是一個真實的值也有可能沒有值。這篇的最后你會知道為什么要用可選值以及如何安全地使用它們。

這些基礎會讓你快速開始Swift之路,做好接觸更高級編程主題的準備。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容