第 2 章
使用 Playgrounds 來體驗Swift

現在你已經設置完 iOS App 開發所需的環境,再繼續往下之前,我先回覆另一個初學者常見的問題,很多朋友問我開發 iOS App 之前需要先具備哪些技巧,簡單的說,這可以歸納成以下三個部分:

  • 學習 Swift - Swift 現在是撰寫 iOS App 所推薦的程式語言。
  • 學習 Xcode - Xcode 是你設計 App UI 、撰寫 Swift 程式與建構你的 App 所需的開發工具。
  • 了解 iOS 軟體開發工具(software development kit)- Apple 提供軟體開發工具讓開發者能夠更輕鬆開發程式。這個套件內建一組軟體開發工具與 API 可以讓開發者強化 iOS App 的開發能力。舉例來說,如果你要在你的 App 中呈現一個網頁,這個SDK 提供內建的瀏覽器,可以讓你馬上在 App 中嵌入它。

你需要具備以上三項的相關知識,而且還有許多的東西要學習,不過別擔心,當你學習完本書之後,你將學習到這些能力。

我們開始來談點有關 Swift 的一些歷史吧。

2014 年的世界開發者會議(Worldwide Developer Conference ),Apple 推出了一個新的程式語言,稱作「Swift」,這讓所有的開發者大為吃驚。Swift 標榜是一個「快速、現代、安全、互動」的程式語言。這個語言很容易學習,而且內建許多功能,可以讓程式開發更有效率。

在Swift 宣布之前,iOS Apps 主要是以 Objective-C 來撰寫。這個語言已經用了將近20 年,被Apple 選為Mac 與iOS 的主要開發語言。我和許多熱衷 iOS 開發的程式設計師談過,絕大多數認為Objective-C 難以學習而且語法看起來很怪異。簡單地說,這些程式碼嚇跑了一些想學習 iOS 程式開發的初學者。

Swift 的推出,或許是 Apple 對這些看法的答案吧!這個語法能夠更簡潔地閱讀。我從 Swift 推出 Beta 版時就開始學習,至今已經超過三年,我可以保證使用Swift 的開發效率會更高,它絕對會加速開發過程。一旦你習慣使用Swift 程式,你可能再也回不去 Objective-C 了。

在我來看,Swift 將會吸引更多的網頁開發者或者是初學者來開發 App。如果你是一個擁有任何一種腳本語言(script language )程式經驗的網頁開發者,你可以利用現有的專業技術來擴充在 iOS 的開發知識,對你來說,學習Swift 會非常容易。這麼說好了,即使你是一個完全沒有程式開發經驗的初學者,以 Swift 來開發 App 的話,你會發現這個語言讓人感覺更友善且自在。

在2015 年,Apple 宣布了Swift 2,並且將這個程式語言變成開源(open source )。這真是一件大事,自此之後,開發者使用這個語言開發了一些有趣且非常棒的開源專案。你不只可以使用 Swift 來開發 iOS App,像是 IBM這樣的公司已經開發出網頁框架(web framework ),可以讓你以 Swift 來建立 Web App。現在你也可以在 Linux 執行 Swift 。

緊接著 Swift 2 之後,Apple 於 2016 年 6 月推出了 Swift 3。這個版本的程式語言已整合到 2016 年 9 月所釋出的 Xcode 8 中,這也是這個語言誕生以來的其中一個重要的版本。Swift 3 有許多的變更,API 被重新命名,並推出了更多的功能。這些變更都讓這個語言變得更棒,開發者可以撰寫更漂亮的程式碼。也因此,也因此,開發者花了很多功夫將專案轉移至新版本。

而在 2017 年 Apple 推出了 Swift 4 與 Xcode 9,並做了更多的強化與改善,最新的 Swift 版本 能夠向下相容,這表示由Swift 3 所開發的專案也可以在 Xcode 9 運行而不會有所改變。如果你必須要做變更,從 Swift 3 變更到 Swift 4也會比 2.2 版轉移到 3 來的簡單許多。

去年,Apple 針對 Swift 的更新幅度不大,版本已經來到 4.2 版, 儘管不是大幅度的變動,這個新版本還是做了許多語言效能提升的改善。在 2019 年三月,Apple 正式釋出 Swift 5,這也是這門程式語言的關鍵里程碑,這個版本加入了許多新的功能,最重要的變更是,Swift 即時運行(runtime)已經加入了 Apple 平台的操作系統,其中包括 iOS、macOS、watchOS 與 tvOS。其實對於開發者來說這是一件好事,從某個角度來看,這表示 Swift 語言已經變得更穩定與成熟了。所有本書所學的內容將能因應未來 Swift 的新版本。

如果你完全是個初學者,你可能心中會有些疑問,為什麼 Swift 會不斷變更呢?如果它不斷更新,Swift 到底能不能用?

幾乎所有的程式語言一段時間就會變更,Swift 也不例外,每年新的語言功能都會加入 Swift 讓這個語言能更強大,並且能夠更有利於開發者來開發。這跟我們的語言一樣,譬如說英語,也是一段時間就會有所變更,像是 freemium(免費加值)這樣的單字與片語每年都會加入字典中。

所有的程式語言一段時間就會做更新,理由有很多種,就跟英文一樣。

Source: https://www.english.com/blog/english-language-has-changed

雖然 Swift 不斷進化,這並不表示它還不能被真正來被運用。如果你想建構一個 iOS App, 你應該使用 Swift。事實上它已經成為 iOS App 的開發標準,像是 LinkedInDuolingoMozilla 從最初的版本都已經完全使用 Swift 來撰寫,自從 Swift 4 的釋出,這個程式語言變得更穩定,絕對適合企業與一般開發來使用。

我們開始吧

對於背景與歷史就談到這邊,我們開始來看一下 Swift。

在嘗試開始使用 Swift 程式語言之前,我們先來看一下以下的程式碼。

Objective-C

const int count = 10;
double price = 23.55;

NSString *firstMessage = @"Swift is awesome. ";
NSString *secondMessage = @"What do you think?";
NSString *message = [NSString stringWithFormat:@"%@%@", firstMessage, secondMessage];

NSLog(@"%@", message);

Swift

let count = 10
var price = 23.55

let firstMessage = "Swift is awesome. "
let secondMessage = "What do you think?"
var message = firstMessage + secondMessage

print(message)

第一段程式碼是以 Objective-C 來撰寫的,第二段則是使用 Swift 程式語言,以上哪一種語言你比較喜歡呢?我猜你可能比較喜歡使用 Swift 來編寫,特別是你對 Objective-C 語法感到失望的話。它比較清楚易於閱讀。每一段敘述結束的地方都沒有@的符號與分號,以下這兩段敘述是將第一段與第二段訊息串接在一起。我相信你已經猜到以下這段 Swift 程式的意義了。

var message = firstMessage + secondMessage

不過,你可能會對以下的 Objective-C 程式感到困惑吧:

NSString *message = [NSString stringWithFormat:@"%@%@", firstMessage, secondMessage];

在 Playgrounds 中測試一下 Swift

我不想直接列出程式碼來讓你感到厭煩,了解程式最好的方法就是實際撰寫程式。Xcode 有一個內建的功能稱作 Playgrounds 。 它是一個讓開發者能夠實驗 Swift 程式語言的互動開發環境,它也可以讓你即時的見到程式運行後的結果。待會你將會了解我的意思與 Swift Playgrounds 的運作方式。

假設你已經安裝完 Xcode 11(或以上的版本),啟動這個應用程式(點選Launchpad 的Xcode 圖示),然後你應該會看見一個啟動對話視窗。

圖 2-1. 啟動對話視窗
圖 2-1. 啟動對話視窗

Playground 是 Xcode 檔的特別型態。你只要點選「Get started with a playground 」,就會出現一個選擇模板的提示,因為我們重點是在 iOS 環境中探索 Swift,在 iOS 區塊下,選取 Blank 來建立一個空白檔,接著點擊 Next 來繼續。

圖 2-2. 建立一個 Playground 檔
圖 2-2. 建立一個 Playground 檔

一旦你要確認儲存這個檔案,Xcode 打開 Playground 介面。你的畫面看起來像這樣:

圖 2-3. Playground 介面
圖 2-3. Playground 介面

在畫面的左側面板,它是讓你輸入程式碼的編輯區。當你想要在上面測試你的程式碼時,按下 Play 按鈕,Playground 會立即解譯你的程式碼(執行程式到 Play 按鈕上方那一行),並將結果顯示在右側的面板中。Playground 中有預設兩行的程式碼,如你所見,在你按下第四行的 Play 按鈕後 ,str 變數宣告的結果立即出現在右側面板中。

我將會在 Playground 中撰寫一些程式,請記住這個練習的目的是要讓你體驗 Swift 程式,並學習它的基礎。我不介紹所有的 Swift 特點。我會將重點放在這些主題:

  1. 常數(constant )、變數(variable)與型態推論(type inference)
  2. 控制流程(Control flow)
  3. 集合型態(Collection types ),像是陣列(array)與字典(dictionary)
  4. Optional

這些都是你所需要知道有關 Swift 的基本型態。你將透過例子來學習,不過,我非常確認你將會對一些程式語言的觀念感到困惑,尤其是如果你完全沒有學過程式的話。不過不用擔心,你將會在一些章節發現到我的一些建議,只要跟著我的建議然後繼續學習,另外,如果你卡住了,記得不要忘記休息一下。

酷!我們開始吧。

常數與變數

常數與變數是兩個在程式中的基本元素。變數(或者是常數)的觀念跟你在數學所學的一樣。看一下以下的方程式:

x = y + 10

在這裡,xy 都是變數。10 是一個常數,表示值是沒有改變的。

在Swift,你以 var 關鍵字來宣告一個變數,另外使用 let 關鍵字來宣告一個常數,如果你將以上的方程式寫成程式,看起來如下所示:

let constant = 10
var y = 10
var x = y + constant

試著在 Playground 輸入這些程式碼,然後按下第 5 行的 Play 按鈕,結果如圖 2-4 所示。

圖 2-4. 方程式的結果
圖 2-4. 方程式的結果

你可以任意命名變數與常數名稱,只要確認它們是有意義的命名就好,舉例來說,你可以將同樣的程式撰寫如下:

let constant = 10
var number = 10
var result = number + constant

為了讓你對 Swift 的常數與變數能更清楚了解,輸入以下的程式來變更 constantnumber 的值:

constant = 20
number = 50

接著,按下 shift+command+enter 鍵來執行程式,除了使用 Play 按鈕以外,你可以使用這個快捷鍵來執行程式。

你只是設定新值給常數與變數,不過當你變更常數的值,Xcode 會出現錯誤,number 反而沒有問題。

圖 2-5. Playgrounds中的錯誤提示
圖 2-5. Playgrounds中的錯誤提示

這是 Swift 中,常數與變數的主要差異,當常數以一個值來做初始化,你不能改變它,如果初始化後還想變更其值的話,則使用變數。

了解型態推論

Swift 提供開發者許多功能來撰寫簡潔的程式。其中一項功能是型態推論 。以上這段同樣的程式我們可以很完整的撰寫如下:

let constant: Int = 10
var number: Int = 10
var result: Int = number + constant

每一種 Swift 中的變數有一種型態,在 : 後的關鍵字 Int ,表示這個變數 / 常數的型態是一種整數,如果儲存的值是小數,我們使用 Double 型態。

var number: Double = 10.5

還有其他型態像是 String ,是用在文字型態的資料,而 Bool 則是布林(boolean)值(true/false)。

現在回到型態推論 ,這個 Swift 中強大的功能,當你在宣告一個變數/常數時可以讓你省略型態。Swift 的編譯器(compiler),可以檢查你所提供的預設值來推論出其型態為何。這也是為何我們能像以下這樣,將程式寫得更簡單的原因。

let constant = 10
var number = 10
var result = number + constant

這一個所給定的值(也就是 10)是一個整數,所以其型態會自動設為 Int 。在 Playgrounds ,你可以按住 option 鍵,然後點選任何一個變數名稱來揭示由編譯器所推論出的變數型態。

圖 2-6.按住 option 鍵並選取這個變數來揭示其型態
圖 2-6.按住 option 鍵並選取這個變數來揭示其型態

被一些程式的觀念壓得喘不過氣嗎?

先休息一下,不需要一氣呵成來看完整個章節,如果你等不及想馬上建立一個 App,你也可以跳過此章,直接進到下一章,你也可以隨時回來了解 Swift 的基礎。

文字的處理

到目前為止,我們認識了IntDouble 型態的變數,想要在變數中儲存文字資料,Swift 提供了一個稱作 String 的型態。

要宣告一個 String 型態的變數,你使用 var 關鍵字,賦予這個變數一個名稱,並指定初始文字給它。所指定的文字以雙引號(")來包圍,如以下例子:

var message = "The best way to get started is to stop talking and code."

在Playgrounds 輸入以上的程式之後,按下 play 按鈕,你將會在右側面板看到結果。

圖 2-7. 字串立即顯示在右側面板
圖 2-7. 字串立即顯示在右側面板

Swift 提供不同的運算子(operator)與函數(function)(或者是說方法(method))來讓你操作字串。舉例來說,你可以使用加(+)運算子來將兩個字串串連(concatenate)在一起:

var greeting = "Hello "
var name = "Simon"
var message = greeting + name
圖 2-8. 字串串連
圖 2-8. 字串串連

如果你想要將整個句子轉換成大寫呢?Swift 提供了一個內建的方法,稱作 uppercased() ,可以將一個字串轉換成大寫。你可以輸入以下的程式來做個測試:

message.uppercased()

Xcode 的編輯器有自動完成功能,自動完成功能一個很方便的功能,它能夠加快程式撰寫速度。當你輸入 mess ,你會見到一個自動完成視窗,依照你所輸入的文字而提供對應的建議。你只需要選擇 message 並按下 enter 即可。

圖 2-9.自動完成功能
圖 2-9.自動完成功能

Swift 採用點語法來存取內建的方法,以及一個變數的屬性。當你在 message 之後輸入點符號,自動完成視窗會再次彈跳出來,它建議了一串可以由變數所存取的方法與屬性。你可以持續輸入 uppercase() ,或者從自動完成視窗中來選取。

圖 2-10. 方法列表
圖 2-10. 方法列表

當你輸入完成之後,你會馬上見到輸出結果,當我們針對 message 使用 uppercased() ,這個 message 的內容會自動轉換為大寫。

uppercased() 是字串所內建的功能之一,你可以使用 lowercased() 來將 message 轉換成小寫。

message.lowercased()

或者,如果你想要計算一個字串的字元有幾個,程式可以這樣寫:

message.count
圖 2-11. 使用內建的函式來操作字串
圖 2-11. 使用內建的函式來操作字串

字串的串連看起來非常簡單,對吧?你只要使用 + 運算子就可以將兩段字串相加。不過,並不是一切都這麼單純,我們在 Playgrounds 寫下這些程式:

var bookPrice = 39
var numOfCopies = 5
var totalPrice = bookPrice * numOfCopies
var totalPriceMessage = "The price of the book is $" + totalPrice

要將字串與數字混合在一起的情況很常見,在以上的例子中,我們計算書的總價,並建立一個訊息告訴使用者總價為何,如果你在 Playgrounds 輸入這些程式,你會注意到一個錯誤。

圖 2-12. 除錯區 /主控台(Console)
圖 2-12. 除錯區 /主控台(Console)

當 Xcode 在程式中找到一個錯誤,這個錯誤會以一個紅色的驚嘆號,加上一段錯誤訊息的提示,有時候 Xcode 會顯示出這個錯誤可以修復的可能方案,有時候則不會。

要揭示錯誤細節,你可以參考除錯區 / 主控台,如果你的 Playground 沒有見到這個 console ,點擊右上角的除錯區域按鈕。

在我告訴你如何解決這個問題之前,你可以先花個幾分鐘來想一下。

首先,要記得的是,Swift 是型態安全( type-safe )的語言。這表示每一個變數都有一個型態,指出是哪一種值被儲存。請問, totalPrice 的型態是什麼?記得稍早我們所學習的,Swift 可以透過值的檢查來判斷變數的型態。

因為 39 是一個整數,Swift 判斷 bookPrice 的型態是 IntnumOfCopiestotalPrice 也是。

在主控台中顯示的錯誤訊息敘述此 + 運算,不能串接一個 String 變數與 Int。這兩個型態必須要相同才行。換句話說,你必須要先將 totalPriceInt 轉換成 String

你可使用以下的程式來將整數轉換成字串:

var totalPriceMessage = "The price of the book is $" + String(totalPrice)

還有一種方式叫做字串插值(String Interpolations)也可以辦到。你可以像這樣來建立 totalPriceMessage 變數:

var totalPriceMessage = "The price of the book is $ \(totalPrice)"

字串插值是在多個型態中建立一個字串所比較推薦的方式。你可以用括號將變數包覆起來,前面加上一個反斜線來進行字串轉換。

變更完成之後,按下 Play 按鈕並重新執行這段程式,錯誤應該會被修正。

流程控制

關於自信心,我認為你們會意識到,你們的成功不僅僅是因為你們自己所做的一切,而是因為在朋友的幫助下,你們不害怕失敗。如果真的失敗了,爬起來再試一次。如果再次失敗,那就再爬起來,再試一次。如果最後還是失敗了,或許該考慮做些其他的事情,你們能夠站在這裡,不僅是因為你們的成功,而是因為你們不會害怕失敗。

- John Roberts, 美國首席大法官

出處: http://time.com/4845150/chief-justice-john-roberts-commencement-speech-transcript/

每一天,我們做了很多決定,不同的決定會有不同的結果或動作。舉例來說,如果你決定明天要六點起床,你將會為自己煮一頓豐盛的早餐,否則的話,就是到外面去吃早餐。

寫程式時,你會使用到 if-then 以及 if-then-else 敘述來檢查條件,然後決定下一步要做什麼。如果你要將以上的例子寫成程式,看起來像這樣:

var timeYouWakeUp = 6

if timeYouWakeUp == 6 {
    print("Cook yourself a big breakfast!")
} else {
    print("Go out for breakfast")
}

你宣告一個變數 timeYouWakeUp 來儲存你睡醒的時間(以24小時制),並使用 if 敘述來評估一個條件,以決定下一步的動作。這個條件是放在 if 關鍵字後面,這裡我們比較 timeYouWakeUp 的值來看它是否等於6 。這裡的 == 運算子是用來做為比較用。

如果 timeYouWakeUp 等於 6 ,在大括號後面內的動作(或敘述)便會被執行,在程式中,我們只是使用 print 函數在主控台中印出一個訊息。

其他的情況下,則會指定執行 else內的程式區塊來列印另一個訊息。

圖 2-13. if 敘述的例子
圖 2-13. if 敘述的例子

在 Playgrounds,你會在主控台中見到「Cook yourself a big breakfasts!」這個訊息,因為 timeYouWakeUp 的值被初始化為 6 ,你可以試試變更為其他值來看會有什麼樣的結果。

在程式中,條件式邏輯很常見,想像你正在開發一個登入畫面,要求使用者輸入使用者姓名與密碼。這個使用者只能使用有效的帳號才能登入,在這種情況下,你可以使用 if-else 敘述來判斷使用者名稱與密碼。

這個 if-else 敘述,只是 Swift 控制程式流程的其中一種方式,Swift 也提供了 switch 敘述來要執行哪個程式區塊,你可以使用 switch 將上面的例子重寫如下。

var timeYouWakeUp = 6

switch timeYouWakeUp {
case 6:
    print("Cook yourself a big breakfast!")
default:
    print("Go out for breakfast")
}

如果 timeYouWakeup 設為 6 ,則會得到相同的結果,Switch 敘述是把一個值(這裡指的是 timeYouWakeUp 的值)跟 case 內的值做比對,預設的 case 是標示為 default 關鍵字,這跟 if-else 序述的 else 程式區塊很像,如果所比對的值不相符的話,預設(default)的 case 將會被執行。所以如果你將 timeYouWakeUp 的值改為 8 ,它將會顯示 「Go out for breakfast」的訊息。

至於何時要使用 if-else 敘述或者 switch 敘述,並沒有一定的準則,有時候會依照可讀性的原因來使用。譬如說,在年底通常會有一筆年終獎金,此時你要規劃來趟旅行,規劃如下:

  • 如果你獲得 $10000 的獎金(或者更多),你將決定到巴黎或倫敦旅行。
  • 如果你獲得 $5000 至 $9999之間的獎金,你將決定到東京旅行。
  • 如果你獲得 $1000 至 $4999之間的獎金,你將決定到曼谷旅行。
  • 如果獎金少於 $1000 ,則待在家中。

如果你將以上的狀況寫成程式,寫法如下:

var bonus = 5000

if bonus >= 10000 {
    print("I will travel to Paris and London!")
} else if bonus >= 5000 && bonus < 10000 {
    print("I will travel to Tokyo")
} else if bonus >= 1000 && bonus < 5000 {
    print("I will travel to Bangkok")
} else {
    print("Just stay home")
}

>= 是比較運算子(comparison operator),表示「大於或等於」。第一個條件檢查 bonus 的值是否大於或等於 10000 。要同時指定兩種條件,你可以使用 && 運算子。第二個 if 條件是檢查值是否在 5000 與 9999 之間。剩下的程式也很容易看得懂。

你可以使用 switch 敘述來將以上的程式改寫如下:

var bonus = 5000

switch bonus {
case 10000...:
    print("I will travel to Paris and London!")
case 5000...9999:
    print("I will travel to Tokyo")
case 1000...4999:
    print("I will travel to Bangkok")
default:
    print("Just stay home")
}

Swift 有一個非常方便的範圍運算子( ),定義下限至上限的範圍。舉例來說,5000...9999 則表示範圍是從 5000 到 9999。針對第一個 case,10000... 表示一個值是大於10000。

這兩段程式都是相同的,但是你比較喜歡哪種方式呢?這種情況下,我比較喜歡 switch 敘述,可以讓程式更簡潔。不管如何,即使你比較喜歡使用 if 來處理以上的問題,結果是一樣的。當你繼續探索 Swift 程式語言,你將會了解 ifswitch 的使用時機。

了解 Array 與 字典

現在你已經對變數與控制流程有了基本了解,我們來介紹另一個常會用到的程式觀念。

到目前為止,我們使用的變數只能儲存一個值。參考一下我們稍早的程式, bonustimeYouWakeUptotalPriceMessage ,不論變數型態為何都只可以存放一個值。

我們以這個範例來看,想像你正在建立一個書架的應用程式,這個應用程式可以將你所蒐藏的書分門別類。在你的程式中,你可能會有一些變數存放你的書的標題:

var book1 = "Tools of Titans"
var book2 = "Rework"
var book3 = "Your Move"

除了在每一個變數儲存一個值之外,是否有其他方式能夠儲存更多的值呢?

Swift 提供一個型態,稱作 Array ,可以讓你在一個變數中,儲存多個值。有了陣列,你可以像這樣儲存標題:

var bookCollection = ["Tool of Titans", "Rework", "Your Move"]

你可以使用一串值來初始化一個陣列,並將值以逗號分開,然後以方括號包圍起來。同樣的,因為 Swift 是一種型態安全的語言,所有的必須要是相同的型態(譬如字串)。

如果你才剛學程式,可能會對陣列值的存取感到奇怪。在 Swift,使用下標語法(subscript syntax )來存取陣列元素。第一個項目的索引是零。因此,參照一個陣列的第一個項目,寫法如下:

bookCollection[0]

如果在 Playgrounds 輸入以上的程式,並且按下 Play 按鈕的話,你應該會在輸出面板見到「Tool of Titans」。

當你以 var 宣告一個陣列,你可以修改它的元素。舉例來說,你可以像這樣呼叫 append 這個內建的方法來加入一個新的項目至陣列中:

bookCollection.append("Authority")

現在陣列有四個項目。那個該如何知道一個陣列的全部數呢?使用內建的 count 屬性:

bookCollection.count

請教一下,要怎樣才能把陣列的每一個元素的值輸出到主控台呢?

不要馬上看解答。

試著想一下。

好的,你可能會將程式撰寫如下:

print(bookCollection[0])
print(bookCollection[1])
print(bookCollection[2])
print(bookCollection[3])

這樣沒有問題,不過還有更好的方式。如你所見,上面的程式都是重複性的,如果陣列有100 個項目,要打一百行程式碼是很無趣的。在 Swift 中,你可以使用 for-in 迴圈,以特定的次數,來執行一個任務(或一段程式)。舉例來說,你可以將上面的程式簡化如下:

for index in 0...3 {
    print(bookCollection[index])
}

這裡指定特定範圍數(0...3 )來做迭代(iterate),在這個情況下,for 迴圈的內的程式碼總共執行 4 次。 而 index 的值會跟著做變更。當 for 迴圈第一次開始執行,index 的值設為 0 ,它會輸出 bookCollection[0] 。當敘述執行完成之後,index 的值會更新為 1 ,接著會輸出 bookCollection[1]。整個過程重複持續直到所設的範圍(也就是 3)為止。

現在問題來了,如果陣列中有 10 個項目該怎麼做呢?你可能會將範圍從 0...3 改為 0...9。那麼如果項目增加到 100 個呢?那麼你會將範圍改成 0…100

有沒有更合適的方法,不需要每次數量改變都要更新程式一次呢?

你有注意到0...30...90...100 這些範圍的模式嗎?

範圍的上限等於全部項目數減一,其實你可以將程式重寫如下:

for index in 0...bookCollection.count - 1 {
    print(bookCollection[index])
}

如此,現在不管陣列項目的數量為何,程式都不會有問題。

Swift 的 for-in 迴圈,提供另外一種針對陣列來做迭代的方式。範例程式可以重新撰寫如下:

for book in bookCollection {
    print(book)
}

當陣列(也就是 bookCollection )被迭代後,每一次迭代的項目會被設定給 book 常數。當迴圈第一次開始後,bookCollection 內的第一個項目設定給 book 。下一次的迭代陣列的第二個項目將會指定給 book。這個過程持續進行直到達到最後一個陣列項目。

現在相信你已經了解 for-in 的運作方式,並知道如何使用迴圈來重複一個工作,我們來討論另一個常見的集合型態,稱作字典(dictionary)

字典跟陣列很相似,可以讓你在一個變數/常數中儲存多個值。主要的差異在於,字典中的每一個值會關聯一個鍵(key),這裡不使用一個索引(index)來識別一個項目,你可以使用唯一的 key 來存取項目。

讓我繼續使用這個 book 集合做為例子。每一本書有一個唯一的 ISBN(國際標準書號,International Standard Book Number 的縮寫 ),如果你想要將每本書以它的 ISBN 來作為其索引值,你可以像這樣宣告與初始化一個字典:

var bookCollectionDict = ["1328683788": "Tool of Titans", "0307463745": "Rework", "1612060919": "Authority"]

這個語法跟這陣列初始化的語法非常相似。所有的值都被一對方括號包覆起來,鍵與值分別以一個冒號(:)來分開。在範例程式中,其鍵為 ISBN。每一本書都關聯一個唯一的 ISBN。

那麼你該如何存取一個特定的項目呢?同樣的,跟陣列非常相似,不過這裡不使用數字索引,而是使用一個唯一的鍵。範例如下:

bookCollectionDict["0307463745"]

這會回傳一個值給你:Tool of Titans 。要迭代字典中的所有項目,你也可以使用 for-in 迴圈:

for (key, value) in bookCollectionDict {
    print("ISBN: \(key)")
    print("Title: \(value)")
}
圖 2-14.對一個字典做迭代
圖 2-14.對一個字典做迭代

你可從主控台中的訊息看出,項目的排序並沒有依照初始設定的順序,跟陣列不一樣,這是字典的特性,項目是以無排序的方式來儲存。

你可能想說,建立一個 App 時,何時會用到字典。我們以另外一個例子來看,它之所以會稱作字典有一個理由。想一下你是如何使用字典呢,在字典中查詢一個字,它會標示字的意義。在這種情況下,這個字就是鍵,而它所代表的意義也就是值。

在進到下一節之前,我們以一個很簡單範例來建立一個表情符號(Emoji)字典,這個字典存放表情符號的意義,為了簡單點,這個字典內有以下幾個表情符號與其意義:

  • 👻 - Ghost
  • 💩 - Poop
  • 😠 - Angry
  • 😱 - Scream
  • 👾 - Alien monster

你知道如何使用字典型態來實作表情符號字典呢?以下是表情符號字典的程式架構,請填入缺少的程式,來讓它能夠運作:

var emojiDict = // 填入初始化字典的程式 //

var wordToLookup = //填入鬼臉表情符號
var meaning = // 填入存取值的程式 //

wordToLookup = //填入生氣表情符號
meaning = // 填入存取值的程式 //

要在 Mac 上輸入表情符號,按下 control-command+space 。

你能夠完成這個練習嗎?

我們來看圖 2-15 的解答以及其輸出結果。

圖 2-15. 表情符號字典練習的解答
圖 2-15. 表情符號字典練習的解答

我相信你已經自己找出解答了。

現在我們加入幾行程式來輸出 meaning 變數至主控台。

圖 2-16. 輸出 meaning 變數
圖 2-16. 輸出 meaning 變數

你會注意到兩件事:

  1. Xcode 指出這兩個輸出的敘述都有些問題。
  2. 主控台區的輸出跟我們之前經歷過的有所不同。結果是正確的,但是 Optional 是什麼意思呢?
Note: 在 Xcode,警告是以黃色來標示。警告跟錯誤的差異在於,即使有些警告,你的程式還是能夠運行,如同名稱所建議的,警告是預先知會你有一些問題。你最好能夠修復這些問題,以免有潛在的問題。

這兩個問題都跟 Swift 中的一個稱作 Optionals 的新觀念有關。即使你有一些程式背景,對你來說這個觀念可能是新的。

我希望你能夠喜歡到目前為止的內容。如果有任何問題而卡住的話,不妨休息一下。喝杯咖啡來放鬆,或者你可以跳過這個章節剩下的部分,然後進到下一章來試著建立你第一個 App,你隨時可以回來複習本章。

了解 Optionals

你有過這樣的經驗嗎?你打開一個 App,按一個按鈕,然後突然就閃退,我相信你應該都碰過。

為什麼 App 會很常閃退呢?一個比較常見的原因是,在運行期間,App試著要存取一個沒有值的變數,所以便發生了這個例外事件。

那麼是否有任何方式可以避免閃退呢?

不同的程式語言有不同的策略來鼓勵程式設計師寫一些好的或者不易出錯的程式碼。Swift Optionals 的導入是協助程式設計師撰寫更好的程式買來避免 App 閃退的方式。

一些開發者很難以理解 Optionals 的觀念。它的基礎觀念其實十分簡單。在存取一個沒有值的變數之前,Swift 會建議你先做個檢查,你必須要確認先確認它有存在一個值才能繼續。這樣才能避免閃退的發生。

直到目前的內容,所有我們所建立的變數或常數都有一個初始值。這在 Swift 是必要的。一個非optional 的變數一定要有一個值。如果你試著宣告一個變數而沒有賦值的話,你會得到錯誤,你可以在 Playgrounds 中做個測試,看會發生什麼結果。

圖 2-17. 宣告一個變數 / 常數而沒有賦予初始值
圖 2-17. 宣告一個變數 / 常數而沒有賦予初始值

在某些情況下,你需要宣告一個沒有初始值的變數。譬如你正在開發一個有註冊形式的 App。不是所有的欄位都是必填,有一些欄位(譬如工作職稱)是可以選填的。在這種情況下,那些欄位可以不需要任何值。

技術上,optional 只是 Swift 中的一個型態,這個型態指出,一個變數可以有指定值或沒有值,要宣告一個變數為 optional ,你可以在變數後面加上個問號( ? )。如下所示:

var jobTitle: String?

你宣告一個名稱為 jobTitle 字串型態的變數,它也是一個 optional,如果你將上面這行程式輸入 Playgrounds 中,將不會出現錯誤,因為 Xcode 知道 jobTitle 這個變數可以沒有值。

如果是非 optional 型態變數,編譯器能夠由變數的初始值推導出其型態,optional變數則必須清楚地指定它的型態(譬如 String, Int )。

如果你有跟著我的指示,在 Playgrounds 輸入這些程式碼( 並且按下 Play 按鈕 ),你可能會注意 nil 顯示在結果的面板上。針對任一個沒有值的 optional 變數,會指定一個特別的值,稱作 nil

圖 2-18. 針對無值的 optional 變數指定一個特別的「nil」值
圖 2-18. 針對無值的 optional 變數指定一個特別的「nil」值

換句話說,nil 表示變數沒有值。

如果你需要指定一個值給一個 optional 變數,你可以像往常一樣指定,如下所示:

jobTitle = "iOS Developer"

現在你應該對 optional 有些概念,但是我們該如何利用它來寫出更好的程式呢?

試著如圖 2-19 來輸入程式。

圖 2-19. 當你存取一個optional 變數時,便發生錯誤
圖 2-19. 當你存取一個optional 變數時,便發生錯誤

只要你輸入完以下的程式,Xcode 會指示一個錯誤訊息。

var message = "Your job title is " + jobTitle

這裡的 jobTitle 被宣告為一個 optional 變數。Xcode 告訴你該行程式有潛在的錯誤,因為 jobTitle 應該是沒有值。你必須在使用optional 變數之前要先做個檢查。

這是 optionals 避免你寫出有問題的程式的方法。無論你何時需要存取一個 optional 變數,Xcode 會強迫你執行確認來看這個optional 是否有值。

強迫解除

所以你該如何執行這樣的確認,並解除(unwrap)optional 變數的值呢?Swift 提供幾個方法。

首先就是所謂的 if 敘述與強迫解除( forced unwrapping)。簡單來說,你使用 if 敘述來跟 nil 做判斷即可檢查這個 optional 變數是否有值。如果這個 optional 真的有值,你可以釋放出它的值來做進一步的處理。

程式如下:

if jobTitle != nil {
    var message = "Your job title is " + jobTitle!
}

這個 != 運算子表示「不等於」。所以如果 jobTitle 不等於 nil,它必定有個值。你可以執行 if 敘述內的程式。當你需要存取jobTitle 的值的時候,你必須要加入一個驚嘆號 ( ! )至 optional 變數的後面。這個驚嘆號是一種特別的指示符號,是用來告訴 Xcode ,你確認這個optional 變數有個值,表示它是安全的,你可以放心使用它。

Optional Binding

強迫解除是存取一個 optional 變數下值的一種方式。另外一種方式稱作 optional binding ,而這是比較推薦作為處理 optionals 的方式。至少,你不需要使用 !

如果使用 optional binding,同樣的程式可以重寫如下:

if let jobTitleWithValue = jobTitle {
    var message = "Your job title is " + jobTitleWithValue
}

你使用 if let 來找出 jobTitle 是否有值。如果有的話,這個值會被指定給暫時的常數 jobTitleWithValue 。在程式區塊中,你可以像平常一樣使用 jobTitleWithValue 。如你所見,並不需要加入! 這個符號。

你必須給這個暫時常數一個新的名稱嗎?

不,其實你可以像這樣使用同樣的名稱:

if let jobTitle = jobTitle {
    var message = "Your job title is " + jobTitle
}
Note:即使名稱是相同的,上面的程式實際上有兩個變數。黑色字體的jobTitle 是optional 變數,而藍色字體的 jobTitle  是作為被指定optional 值的暫時常數

這些大約是有關 Swift optionls 的內容。是否你對於 ?! 的符號感到困惑呢?我希望你沒有問題,倘若你還是不甚了解 optional 的話 ,你可以將你的問題刊登在我們的臉書社團(https://www.facebook.com/groups/appcodatw/)

好的,你還記得在圖 2-16 的警告提示嗎?當你試著輸出 meaning , Xcode 給你一些警告。在主控台中,即使值都已經輸出,但它在前面會寫著「Optional」。

Figure 2-20. 跟圖 2-16 一樣出現了警告訊息
Figure 2-20. 跟圖 2-16 一樣出現了警告訊息

現在你已經知道原因為何了吧?為何這個 meaning 變數是一個 optional 呢?那麼該如何修改程式來移除這個警告訊息呢?

同樣的,先不要看解答,自行思考一下。

如果你仔細看一下程式, meaning 實際上是一個 optional。這是因為字典針對所指定的鍵可能沒有對應的值,舉例來說,如果你在Playgrounds 將程式寫成這樣:

這個 meaning 變數會被指定為 nil,因為 emojiDict 並沒有針對鍵 😍的對應值。

因此,無論何時我們需要存取 meaning 的值時,我們必須要先檢查是否有值,要避免產生警告訊息的話,我們可以使用 optional binding 來測試值是否存在。

圖 2-21. 使用 optional binding 來檢查 meaning 是否有值, 然後解除它
圖 2-21. 使用 optional binding 來檢查 meaning 是否有值, 然後解除它

變更完成之後,這個警告便會消失,你也會注意到顯示在主控台中的值前面不再加上 Optional 了。

玩一下UI

在完成本章之前,我們來點好玩的,建立一些 UI 元件。我們準備要做的是在一個視圖(view)中顯示一個表情符號的圖示,還有它對應的意義(如圖2-22所示),取代輸出在主控台中。

圖 2-22. 在視圖中顯示表情符號
圖 2-22. 在視圖中顯示表情符號

如同我在本章最前面所述,為了建立 App ,除了Swift 的學習之外,你還需要熟悉 iOS SDK。

你也可以使用 Playgrounds 來探索一些由iOS SDK 所提供的 UI 控制(control)。視圖是在 iOS 中的基本元件。你可以把它想做是一個用來顯示內容的長方形區域。現在至你的 Playground 中,輸入以下的程式,然後按下 Play按鈕來執行程式:

你應該對前面幾行很熟悉了。我們將重點放在程式的最後兩行。要建立一個視圖,你可以以它的大小來實例化( instantiate)一個 UIView 物件。這裡我們將寬度與高度分別設為 300 點。xy 指定其位置。最後一行則是將視圖的背景顏色改為橘色

要在 Playgrounds中預覽 UI,你可以點擊快速預覽(Quick Look )或者顯示結果(Show Result)圖示。這個快速預覽功能以彈出的方式顯示這個標籤,如果你是使用顯示結果功能,Playground 會將視圖顯示在程式內,位於你的程式的下方。

圖 2-23. 快速預覽與顯示結果
圖 2-23. 快速預覽與顯示結果

現在這個視圖(也就是 containerView)是空的。如果你能夠加入表情符號至視圖中是不是更好呢?

繼續輸入以下的程式並點擊 Show Result 圖示。

let emojiLabel = UILabel(frame: CGRect(x: 95, y: 20, width: 150, height: 150))
emojiLabel.text = wordToLookup
emojiLabel.font = UIFont.systemFont(ofSize: 100.0)

containerView.addSubview(emojiLabel)

表情符號只是一個字元。在iOS,你可以使用標籤(label)來顯示文字。要建立一個標籤,你可以你想要的大小來實例化一個 UILabel 物件。這裡標籤是設為 150x150 點(point)。而 text 屬性則是包含了要顯示在標籤上的文字。為了讓文字更大一些,你可以變更字體(font)屬性來採用較大的字體大小。最後,要將標籤顯示在containerView,你要呼叫 addSubview 來把標籤加入視圖中。

繼續輸入以下的程式來加入另外一個標籤:

let meaningLabel = UILabel(frame: CGRect(x: 110, y: 100, width: 150, height: 150))
meaningLabel.text = meaning
meaningLabel.font = UIFont.systemFont(ofSize: 30.0)
meaningLabel.textColor = UIColor.white

containerView.addSubview(meaningLabel)

執行程式之後,點選 Show Result 按鈕來檢視結果如下:

圖 2-24. 書呆子表情符號顯示在視圖中
圖 2-24. 書呆子表情符號顯示在視圖中

這就是 iOS SDK的強大之處。它內建的大量預建的元素,讓開發者透過幾行程式就可以自訂它們。

不要誤會了,本書之後的內容,並不需要你寫程式來建立使用者介面。Xcode 提供一個介面建構器(Interface Builder)的功能,可以讓你使用拖曳與置放的方式來設計 UI。我們下一章會進一步做介紹。

現在你已經嘗試過 Swift了,感覺如何?喜歡嗎?我希望你發現 Swift 更容易學習與撰寫。最重要的是,我不希望嚇跑你,而不學習 App 的開發。

接下來的內容

接下來我將教你如何建立你第一個 App。你現在可以馬上進到下一章。不過,如果你想要學習有關 Swift 程式語言,我建議你要看一下 Apple官方的程式語言指南(https://docs.swift.org/swift-book/)。你將學會這個語言的語法,了解函數( function)、optionals 以及其他內容。

但這不是馬上需要做的工作。

如果你等不及馬上建立你第一個 App,翻到下一章,之後再來閱讀這份指南,你可以學習到更多有關Swift 的內容。

本文摘自《iOS 13 App程式設計實戰心法》一書,博碩授權轉載。如果你想更深入學習Swift程式設計和下載完整程式碼,你可以從AppCoda網站購買完整電子版。

results matching ""

    No results matching ""