筆記:Develop in Swift Fundamentals_Optionals

薛丁格的貓?

先前的相關練習

學習目標

  • 有一個特殊「盒子」叫做可選型別,用來存可能有值或沒值的數據。
1.怎麼做一個可能沒東西的"盒子"
2.怎麼看這個"盒子"裡面有沒有東西
3.怎麼安全地從"盒子"裡拿出東西,而不會出錯
4.怎麼讓函數給予一個可能有值或沒值的"盒子"
5.有些時候可以直接確定"盒子"裡一定有東西,該怎麼做
  • Failable Initializer (可失敗的初始化)
"可失敗的初始化"就像一個食譜,當你試著按照食譜做菜,但材料不足或方法不對時,料理就可能做不出來。
- 在程式裡,當我們嘗試建立一個新的物件,但提供的資料不符合條件時,它就會告訴我們"做不出來",返回一個nil(代表什麼都沒有)。
  • Force-unwrapping(強制解包)
想像可選值是一個禮物盒,裡面可能有禮物,也可能是空的。
使用驚嘆號(!)就像是你確定裡面有禮物而直接打開盒子。
但如果裡面什麼都沒有,你會非常失望到整個心情崩潰。在程式裡,這種"心情崩潰"就是程式當機。
所以,除非你非常確定盒子裡有東西,否則最好不要隨便使用感嘆號(!)。
  • An implicitly unwrapped
想像隱式解包的可選值像是一個透明的禮物盒。
當你首次放入禮物後,你就預期這個盒子裡面永遠有禮物,所以每次你都可以直接打開它而不需要檢查裡面有沒有禮物。
但如果你打開盒子的時候裡面是空的,你會非常震驚,程式也會因此崩潰。
所以,即使盒子是透明的,也要確保裡面真的有禮物再打開它。
  • Nested Optionals
你可以想像一個大盒子裡面有另一個小盒子。
這個大盒子可能有或沒有小盒子(所以它是一個可選值),而那個小盒子裡面可能也有或沒有禮物(也是一個可選值)。
所以,要取得最裡面的禮物,你可能需要打開兩個盒子,這就是所謂的"巢狀可選型別"。
  • Optional binding
可選綁定(Optional binding)就像是你不確定你的口袋裡是否有鑰匙。
你會嘗試摸一摸,如果有鑰匙,你就拿出來用;如果沒有,就繼續做其他事。
在程式中,我們使用這個技巧來確認一個變數裡面是否真的有值,然後安全地使用它。
  • Optional chaining
可選連接(Optional chaining)就像是一連串的取物動作。想像你要從家中的櫃子裡取出一個盒子,然後再從盒子裡取出物品。
如果任何一步中你找不到你想要的,整個過程就會停止。在程式裡,我們使用這種方法來取得連在一起的多個可選值,如果其中一步取不到值(也就是nil),整個操作就會立刻停止。

Nil

1.在程式中,有時會遇到一個情況,某些數據可能存在,也可能不存在。
2.有一種特殊的數據類型,叫做 Optional ,用於表示一個值可能存在或可能不存在。
3.可以想像 Optional 就像一個盒子,裡面可能有東西,也可能什麼都沒有(nil)。

- 這裡以一個書店應用為例:當一本書尚未出版時,如何表示那本書的出版年份?
- 不能給它一個不真實的年份,這會造成誤解。可使用 Optional 方法來處理這種情況。
- 當我們還不知道一本書的出版年份時,我們可以把它設定為nil,代表還不確定。
還不知道出版時間,使用nil

Specifying the Type of an Optional

想像一下「可選值」就像是一個禮物盒。這個禮物盒裡面或許有東西(比如玩具),或許是空的。
但無論如何,你都得告訴人家這個盒子是為了什麼東西而設計的,比如說玩具盒。
使用「可選型別」

Working with Optional Values

這邊用巧克力來練習
  • 檢查可選值有無內容?
1.使用 if 語句檢查可選值是否為 nil。
檢查是否有巧克力
  • 強制解包(Force Unwrap)取值
2.若確定可選值內有內容,可使用 ! 取得其值,但有風險。若解包時其值為 nil,程式會崩潰。
因為chocolateBox為nil。當強制解包時會error
  • ## 可選值綁定(Optional Binding)##
更安全的方法,確保在拆封可選值之前,它確實有內容。此方法能夠將可選值「安全地」拆封到一個非可選的常數或變數中。
- 這樣,不再需要兩步驟(先檢查再取值),只需要一個步驟就能知道結果
- 確認盒子裡是否有巧克力。如果確定有巧克力,就馬上取出它。
可以在「打開盒子」的同時,確認盒子裡是否有巧克力

Functions and Optionals

想像在一間書店工作,顧客會給予一串編號,我們要根據這編號去找書。
  • 轉換與不確定性
有時候編號真的對應到一本書,但有時候可能對應不到(也許編號錯誤或是該書店沒有這本書)。

1. 不確定性:
- 在Swift中,有些動作或函式可能無法確定每次都會成功。因此Optional來表示一個值可能存在也可能不存在。

2. 轉換可能失敗:
- 當嘗試從一個字串轉換為整數時,這個轉換可能會成功,也可能會失敗(例如,因為該字串包含非數字字符)。
因此,該轉換函式回傳一個可選的整數。
有些動作或函式可能無法確定每次都會成功。
當嘗試將一個字串轉換為整數時,這個轉換函式會回傳一個可選的整數(Int?)。
  • 函式與可選值的互動:
設計函式時,使其參數或回傳值為可選型別。這允許更大的彈性,因為不是所有情況下參數或回傳值都會有具體的值。

1. 可選值在函式的參數中:
- 如果參數有時存在、有時不存在,可使用可選型別。如:聯絡人資訊

2. 函式的回傳值也可能是可選的:
- 當函式不一定每次都會有結果回傳時,其回傳值應為可選型別。如:網址可能無法取得文字。
可選值在函式的參數中
函式的回傳值也可能是可選的

Failable Initializers

當一個 Initializer 有機會返回「空值」(nil)時,稱之為「Failable Initializers」。
- 就像之前的例子,嘗試將文字轉為數字的過程:若文字不符合數字的格式,Initializer就會返回一個nil,表示轉換失敗。
  • 自訂 Failable Initializers,根據某些條件來決定是否創建一個實例或返回 nil
就像一家只賣特定尺寸衣服的商店:

1.商店定義:
- 你有一家只賣1歲到3歲大小衣服的幼兒服裝店。這家店的規定很簡單,只有1到3歲的衣服大小。

2.挑選衣服:
- 當客戶來詢問,希望為「Joanna」這名14個月大的幼兒挑選衣服時,店家可以提供適合的衣服。

3.尺寸限制:
- 但如果有人要求為2個月或者4歲的幼兒挑選衣服,店家會說:「很抱歉,我們這裡沒有適合的尺寸。」

4.確認購買:
- 只有當店家可以提供衣服時,客戶才能完成購買,否則就會離開而不買任何東西。
商店定義
挑選衣服
確認購買(有符合)
尺寸限制(不符合)

Optional Chaining

1. Nested Optionals
- 就像一個裡面又有另一個的盒子。例如,一個「人」可能有「住所」,而「住所」又可能有「地址」。

2. 為什麼需要Optional Chaining?
- 當想知道深層裡的某個選擇性值(像是「公寓號碼」)時,不需要寫很多的 if 條件句來一層層地檢查。

3. Optional Chaining是如何運作的?
- 使用「?」來連結每一層的選擇性值。
- 如果某層是 nil,整鍊就會立刻中斷。
- 如果都不是 nil,你就可以直接取得最深層的值。

4. 範例:
- 傳統方式:一層層檢查「人」有沒有「住所」,「住所」有沒有「地址」,「地址」有沒有「公寓號碼」。
- 選擇性鏈結:直接用 `人.住所?.地址?.公寓號碼` 就可以取得「公寓號碼」,如果中間任何一部分是 nil,整句就會返回 nil。
結構
  • 傳統方式(不用Optional Chaining)
一層層檢查「人」有沒有「住所」,「住所」有沒有「地址」,「地址」有沒有「公寓號碼」。

- 首先,你確認大樓是否存在。
- 接著,你確認你的朋友是否住在那棟大樓裡。
- 最後,你詢問他的房間門牌號碼。

如果在任何一步你得到"不知道"的答案,你就必須停下來,因為你無法確定下一步的答案。

- 涉及到多重的 if-let 結構來確保每一個選擇性值都被安全地解包。
- 這可以確保每一步都是安全的,但可能會使代碼變得冗長。
- 這也是為什麼Optional Chaining在Swift中是一個很受歡迎的功能,因為它可以使代碼更簡潔,同時保持安全性。
  • 使用 Optional Chaining
直接用 人.住所?.地址?.公寓號碼 就可以取得「公寓號碼」,如果中間任何一部分是 nil,整句就會返回 nil。

- 只問一個問題:「請問我朋友的房間門牌號碼是什麼?」
- 如果大樓不存在、你的朋友不住在那,或他的房間沒有門牌號碼,答案就是「不知道」。
- 如果所有資訊都確定,你就可以得到他的門牌號碼。

Optional Chaining 允許我們用簡潔的方式連續地存取多層的可選型別。如果鏈中的任何部分為 nil,整個鏈就會立即返回 nil。

Implicitly Unwrapped Optionals

在程式設計中,有時候我們知道某個變數在一開始可能沒有值,但是在接下來的運作中「一定」會有值。Implicitly Unwrapped Optionals 就是為了這種情境設計的。
它讓我們不用每次都去檢查變數是否有值(因為我們知道它「最終會有值」),但同時也能在程式的其他地方保留這個變數可能為空的可能性。

以 iOS 的 `@IBOutlet` 作為例子。當在設計界面時,可能會先把一個按鈕或標籤連接到我們的程式碼,但在程式剛開始運作的時候,這些界面元件還沒有真正「連接」,所以它們的值是 `nil`。但我們知道,一旦畫面被加載,這些元件肯定會有值。
因此,這邊使用 'Implicitly Unwrapped Optionals' ,這樣在其他部分的程式碼中我們可以直接使用這些界面元件,而不用每次都去檢查它們是否有值。

1. Implicitly Unwrapped Optionals:
- 這是一種特殊的可選型別,用 `!` 表示,如 `UILabel!`。我們用它在知道變數稍後一定會有值的情境。

2. 為什麼需要它:
- 有些時候變數一開始可能沒值,但稍後確定會有。使用隱式解包可選型別可以避免重複解包檢查。

3. 要小心使用:
- 過度使用隱式解包的可選型別會增加崩潰的風險。只有在你「非常確定」該變數稍後會有值的情境下才應使用它。

4. 例子:
- 在 iOS 中,`@IBOutlet`(如按鈕或標籤)就是使用隱式解包的可選型別,因為在畫面加載後它們一定會被連接上。

@IBOutlet使用隱式解包的可選型別是因為,在其生命週期的某個時刻,可以確定它會有值,所以不需要每次都去進行安全的解包檢查。

最終一定有值

Exercise — Optionals

Imagine you have an app that asks the user to enter his/her age using the keyboard.
When your app allows a user to input text, what is captured for you is given as a String.
However, you want to store this information as an Int. Is it possible for the user to make a mistake and for the input to not match the type you want to store?

Declare a constant userInputAge of type String and assign it "34e" to simulate a typo while typing age.
Then declare a constant userAge of type Int and set its value using the Int initializer that takes an instance of String as input.
Pass in userInputAge as the argument for the initializer. What error do you get?
觀察錯誤
Go back and change the type of userAge to Int?, and print the value of userAge.
Why is userAge's value nil? Provide your answer in a comment or print statement below.
Now go back and fix the typo on the value of userInputAge. Is there anything about the value printed that seems off?
Print userAge again, but this time unwrap userAge using the force unwrap operator.
修正後,強制解包print出
Now use optional binding to unwrap userAge. If userAge has a value, print it to the console.

App Exercise — Finding a Heart Rate

Many APIs that give you information gathered by the hardware return optionals. 
For example, an API for working with a heart rate monitor may give you nil if the heart rate monitor is adjusted poorly and cannot properly read the user's heart rate.

Declare a variable heartRate of type Int? and set it to nil. Print the value.
In this example, if the user fixes the positioning of the heart rate monitor, the app may get a proper heart rate reading. Below, update the value of heartRate to 74. Print the value.
Optional(74)
As you've done in other app exercises, create a variable hrAverage of type Int and use the values stored below and the value of heartRate to calculate an average heart rate.

If you didn't unwrap the value of heartRate, you've probably noticed that you cannot perform mathematical operations on an optional value.
You will first need to unwrap heartRate.

Safely unwrap the value of heartRate using optional binding.
If it has a value, calculate the average heart rate using that value and the older heart rates stored above.
If it doesn't have a value, calculate the average heart rate using only the older heart rates. In each case, print the value of hrAverage.
解包,因為heartRate是optional

Exercise — Functions and Optionals

If an app asks for a user's age, it may be because the app requires a user to be over a certain age to use some of the services it provides.
Write a function called checkAge that takes one parameter of type String. The function should try to convert this parameter into an Int value and then check if the user is over 18 years old.
If he/she is old enough, print "Welcome!", otherwise print "Sorry, but you aren't old enough to use our app." If the String parameter cannot be converted into an Int value, print "Sorry, something went wrong.
Can you please re-enter your age?" Call the function and pass in userInputAge below as the single parameter. Then call the function and pass in a string that can be converted to an integer.
Go back and update your function to return the age as an integer. Will your function always return a value? Make sure your return type accurately reflects this.
Call the function and print the return value.
回傳值的類型可能是可選的
Imagine you are creating an app for making purchases.
Write a function that will take in the name of an item for purchase as a String and will return the cost of that item as an optional Double.
In the body of the function, check to see if the item is in stock by accessing it in the dictionary stock.
If it is, return the price of the item by accessing it in the dictionary prices. If the item is out of stock, return nil.
Call the function and pass in a String that exists in the dictionaries below. Print the return value.
回傳值的類型可能是可選的

App Exercise — Food Functions

Suppose you want your fitness tracking app to give users the ability to log food.
Once food has been logged, users should be able to go back and see what they ate at a specific meal.

Write a function that takes a String parameter where you will pass in either "Breakfast," "Lunch," or "Dinner."
The function should then return the Meal object associated with that meal, or return nil if the user hasn't logged that meal yet.
Note that a Meal object and a dictionary, meals, representing the meal log have been created for you below.

Call the function one or twice and print the return value.
處理可能不存在的鍵值
Write a function that will check to see if your meal log (a dictionary like that in the previous exercise) is saved to the device.
If it is, return the meal log.
If it isn't, return an empty dictionary of type [String: Any].
The code you should use in this exercise for retrieving something saved to the device is UserDefaults.standard.dictionary(forKey: "mealLog").
This code will return an optional [String: Any].

If it returns a value, that is your meal log. If it returns nil, then no meal log has been saved. Call the function and print the return value.
練習從 UserDefaults 中使用指定的 key("mealLog")來取得一個字典。

Exercise — Failable Initializers

Create a Computer struct with two properties, ram and yearManufactured, where both parameters are of type Int.
Create a failable initializer that will only create an instance of Computer if ram is greater than 0, and if yearManufactured is greater than 1970, and less than 2020.
建立Failable Initializers
Create two instances of Computer? using the failable initializer.
One instance should use values that will have a value within the optional, and the other should result in nil.
Use if-let syntax to unwrap each of the Computer? objects and print the ram and yearManufactured if the optional contains a value.
if let 解包

App Exercise — Workout or Nil

Create a Workout struct that has properties startTime and endTime of type Double. Dates are difficult to work with, so you'll be using doubles to represent the number of seconds since midnight, i.e. 28800 would represent 28,800 seconds, which is exactly 8 hours, so the start time is 8am.

Write a failable initializer that takes parameters for your start and end times, and then checks to see if they are fewer than 10 seconds apart.
If they are, your initializer should fail. Otherwise, they should set the properties accordingly.
建立Failable Initializers
Try to initialize two instances of a Workout object. Unwrap each of them and print its properties.
One of them should not be initialized because the start and end times are too close together.
The other should successfully initialize a Workout object.
解包

--

--

wei Tsao 學習紀錄
彼得潘的 Swift iOS / Flutter App 開發教室

Hi ! 我是wei , 先前未接觸過程式開發設計,想藉此來記錄自己的學習歷程,以利培養自己的程式邏輯 :)