Swift — 面試題挑戰賽(3)

讓我們一起來討論以及研究一些 iOS 面試題目吧。

Jeremy Xue
Jeremy Xue ‘s Blog
11 min readJun 10, 2019

--

Photo by Charles 🇵🇭 on Unsplash

# 本期題目:

— Swift:

  1. What are type aliases?
  2. Raw values and associated values
  3. Non-Escaping and Escaping Closures
  4. Error handling in Swift
  5. Guard benefits

# What are type aliases?

類型別名(Type alias)宣告將現有類型的命名別名引入程序中。使用 typealias 關鍵字宣告,具有下列形式:

typealias 別名 = 現存類型

宣告類型別名後,可以在程序中的任何位置使用別名來代替現有類型。現有類型可以是命名類型或複合類型。類型別名並不會創建新類型,而是允許該名稱引用現有類型。

類型別名宣告也可以使用泛型參數為現有的泛型類型指定名稱。類型別名可以為現有類型的部分或全部泛型參數提供具體類型。例如:

使用泛型參數宣告類型別名時,對這些參數的約束必須與現有類型的泛型參數的約束完全匹配。 例如:

typealias DictionaryOfInts <Key:Hashable> = Dictionary <Key,Int>

前篇 Network Layer 的文章我們有使用類型別名 Header 以及 Parameter 來取代我們 Dictionary 的類型值,如此一來能夠在調用時更加直觀:

# Raw values and associated values

#Associated values

有時將其它類型的關聯值與這些情況一起存儲是很有用的。這樣你就可以將額外的自定義信息和情況儲存再一起,並且允許你的程式碼中使用每次調用這個情況時都能使用它。

下面是一個 Barcode 的範例,其中有兩種帶有不同關聯值的情況:

  • upc 帶有四個 Int 的關聯值
  • qrCode 帶有一個 String 的關聯值

當我們創建該 enum 實例時,也會連帶的賦予其關聯值:

當然我們也能讓關聯值與 switch 語句來進行匹配,同時也能獲取該情況中的關聯值:

# Raw values

關聯值中的 Barcode 範例演示了 enum 情況如何宣告其儲存不同類型的關聯值。而作為關聯值的另一種選擇,枚舉情況可以用相同類型的 rawValue。我們透過在 enum 名稱後方加上類型來實現它。

當你在使用整數或字串原始值的枚舉時,你不必顯式地給每一種情況都分配一個原始值。當你沒有分配時,Swift 將會自動為你分配值。所以可以寫成下面的方式,但其中的 rawValue 與上面相同。

也可以寫為下列方式:

我們可以訪問 Planet 中的 rawValue 來查看該值:

而如果 enum 定義的 rawValue 類型為 String 時,那麼其 rawValue 為該 case 的名稱:

而我們也能夠過 rawValue 來初始化該 enum 的實例,要注意的是透過 rawValue 初始化的值是為該類型的 Optional value。因此我們可以透過 Optional binding 來判斷它是否存在。

# Non-Escaping and Escaping Closures

# Non-escaping closures

當傳遞閉包作為函數的參數時,閉包得到執行函數主體並且返回編譯器。當執行結束時,傳遞的閉包在記憶體中不再存在。非逃逸閉包的生命週期:

  1. 在函數調用期間將閉包作為函數參數傳遞。
  2. 在函數中做一些額外的工作。
  3. 函數運行閉包。
  4. 函數返回編譯器。

# Escaping closures

當傳遞閉包作為函數的參數時,該閉包被保留用於稍後執行並且執行函數主體,返回編輯器。當執行結束時,傳遞的閉包存在在記憶體中,直到該閉包被執行。逃逸閉包的生命週期:

  1. 在函數調用期間將閉包作為函數參數傳遞。
  2. 在函數中做一些額外的工作。
  3. 函數異步執行閉包或存儲。
  4. 函數返回編譯器。

而有幾種方式可以使閉包逃逸:

● 存儲閉包:

我們可以將逃逸閉包傳遞在某個屬性中,當我們稍候需要時在調用即可:

執行結果如下,我們可以看到當我們執行 escapingMethod 時並還沒有印出逃逸閉包內的資訊,直到我們調用了 doEscapingClosure 來執行我們存儲在escapingClosure 中的逃逸閉包:

● 異步執行:

當你在 Dispatch queue 上異步執行閉包時,該佇列將為你保留記憶體中的閉包以便將來使用。在這種狀況下,你不知道閉包何時被執行。

# Error handling in Swift

當有錯誤拋出時,一些周圍的程式碼必須負責處理錯誤。例如,透過更正問題,嘗試替代方法或通知使用者失敗。

當函數拋出錯誤時,它會改變程式的流程,因此您可以快速辨認出程式碼中可能引發錯誤的位置,這一點很重要。 要在程式碼中標識這些位置,請編寫try 關鍵字 — 或 try? 或者 try! 在調用可能引發錯誤的函數、方法或初始器之前。

● 使用函數拋出錯誤:

為了表示一個函數或者方法可以拋出錯誤,在該方法 () 後加上 throws 關鍵字。使用 throws 標記的函數叫做拋出函數(throwing function)。如果它擁有一個返回值,那麼 throws 關鍵字要在返回箭頭 (->) 之前。

func canThrowErrors() throws -> String
func canThrowErrorsWithReturn() throws -> String

拋出函數將拋出其中的錯誤傳播到調用它的範圍。

只有拋出函數才能傳播錯誤。 必須在函數內部處理在非拋出函數內拋出的任何錯誤。

● do-catch 語句:

你可以使用 do-catch 語句透過運行程式碼區塊來處理錯誤。 如果 do 子句中的程式碼拋出錯誤,則會與 catch 子句進行匹配,以確定哪一個可以處理錯誤。以下是 do-catch 語句的一般形式:

你在 catch 之後編寫一個 pattern,來表示該子句可以處理的錯誤。 如果catch 子句沒有 pattern,則該子句匹配任何錯誤並將錯誤綁定到名為 error 的本地常數。

● 將錯誤轉換為 Optional value:

你可以使用 try? 透過將其轉換為 optional value 來處理錯誤,如果在 try? 表達式中拋出錯誤,則表達式的值為 nil,反之則為該類型的 optional value。在下面的範例中,xy 具有相同的值和行為:

func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}

● 取消錯誤傳播

有時你知道拋出函數或方法實際上不會在運行時拋出錯誤。 在這種狀況下,你可以編寫 try! 表達式來取消錯誤傳遞並且把調用放進不會有錯誤拋出的運行時斷言當中。如果錯誤真的拋出了,你會得到一個運行時錯誤。

例如,以下程式碼使用 loadImage(atPath:) 函數在給定路徑下加載圖片資源,如果無法加載圖片則拋出錯誤。 在這種情況下,由於圖片是隨應用程序一起提供,因此運行時不會拋出任何錯誤,因此取消錯誤傳播是適合的。

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

# Guard benefits

如果不滿足一個或多個條件,則使用 guard 語句將程序控制轉移到範圍之外。guard 語句具有以下形式:

guard 語句中任何條件的值必須是 Bool 類型或橋接為 Bool的類型。 條件也可以是 optional binding 的宣告。在 guard 語句條件中從可選綁定中賦值的任何常數或變數都可以用於 guard 語句之後的部分。

guard 語句中的 else 子句是必要的,並且必須使用以下語句之一調用具有Never 返回類型的函數或在 guard 語句的封閉範圍之外程序控制:

  • return
  • break
  • continue
  • throw

更多控制轉換語句,可以查看這篇文章

guard 語句,類似於 if 語句。使用 guard 語句來要求一個條件必須是真才能執行 guard 之後的語句。與 if 語句不同, guard 語句總是有一個 else 分句 — — else 子句裡的程式碼會在條件為 false 的時候執行。

與使用 if 語句進行相同的檢查相比,使用 guard 語句來提高代碼的可讀性。 它會讓正常地寫代碼而不用把它們包裹進else 代碼塊,並且它允許你保留在需求之後處理危險的需求。

當你在某些狀況需要再條件不符合時,中斷後面的程序,那麼使用 guard 語句來提早結束程序是一個很好的方式。下面我們示範一個帳號登入的範例:

上面的範例,我們在判斷帳號密碼的判斷式使用 guard 語句來確認使用者是否有輸入帳號密碼,若沒有輸入可以提早退出登入的流程,若是帳號密碼都有輸入的話才會進行請求資料的動作。

而使用 if 判斷的方式如下:

使用 guard 對於一些能夠需要提早判斷的條件進行判斷,讓程序在該中斷的時候提早退出,避免掉使用過多的 if 或 if-else 判斷式產生的巢狀結構,加上多個 ifif-else 判斷式容易產生不好閱讀的問題。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]