SwiftUI — 不公開透明的 Some

ChunYi LI
One Two Swift
Published in
9 min readJun 6, 2021

身為用慣 UIKit 製作 layout 的開發者一打開新版 Xcode SwiftUI 專案後,先是被整個專案架構完全不一樣給嚇了一跳,再來就是被 SwiftUI View的某個關鍵字給吸引。

struct MyView: View {
var body: “some” View {
Text("Hello, World!")
}
}

就是那個在 View 前面的修飾詞— some。

當你仔細看 MyView 這個 structure 之後,你就會發現他並不是以往用來繼承的 UIView,而是去實作一個名為 View 的 protocol。

然而這個 View 裡面有個 computed property — body,是一個 some View 的型別。

然而滿頭問號的你把 some View 當作關鍵字去 google ,你就會得到這是一個新的型別叫做 — Opaque Types。

Opaque Types

Opaque 翻譯為不透明的,讀做 “歐配可”。

官方文件說明 Opaque return type 是用來取代一個具體型別返回值,用以隱藏不必要資訊顯示給外部的調用者。

A function or method with an opaque return type hides its return value’s type information. Instead of providing a concrete type as the function’s return type, the return value is described in terms of the protocols it supports. Hiding type information is useful at boundaries between a module and code that calls into the module, because the underlying type of the return value can remain private. Unlike returning a value whose type is a protocol type, opaque types preserve type identity — the compiler has access to the type information, but clients of the module don’t. — 官方文件

還是霧煞煞?

我從一個國外的開發者寫的文章上看到一個很喜歡的例子。

健達出奇蛋 — Surprise Egg

還記得健達出奇蛋嗎,這個大家記憶中的零食是生產金沙巧克力的義大利食品商費列羅所推出的產品

其產品概念為一個內有騙小孩用的塑膠玩具錫箔包裝的巧克力蛋,其宣傳為『三個願望,一次達成。』

如果健達出奇蛋這個概念是一個 protocol,他應該會長這樣

protocol SurpriseEgg{
associatedtype ContentType

var wrapper : Wrapper{get} // 包裝紙
var chocolate: Chocolate{get} // 巧克力
var content: ContentType{get} // 玩具
}

associatedtype : protocol內部所使用的泛型,用來標注protocol的參數,然而實際的類型須由實作這個protocol的物件來定義。

此時你為一個商店老闆,你提供了一個販賣健達出奇蛋的轉蛋機,而這個轉蛋機 method 的定義應該會長這樣。

func getSurpriseEgg() -> some SurpriseEgg

客人只要呼叫這個 method 就可以拿到一個

  • 錫箔紙包裝
  • 玩具(至於是什麼顯然製造商不是很care,因為通常很廢。)
  • 巧克力蛋

名為健達出奇蛋的產品。

然而能保證玩具是汽車玩具嗎?能保證是拼圖玩具嗎?
不能,能保證的是你手上的商品一定是一個符合 SurpriseEgg 這個 protocol的物件。

使用 some 這個不透明型別去修飾就代表,你不會知道巧克力的製造方式、包裝上logo的製作方式也不會知道裝玩具的容器是什麼顏色。

與其說不會知道,不如說不用知道,因為這些資訊對於『販賣健達出奇蛋』這個行為是不必要的資訊。

然而我們回到 some View,是不是就大概可以知道這個 SwiftUI 檔到底在講什麼。

struct MyView: View { 
var body: “some” View {
Text("Hello, World!")
}
}
  • 第一行 struct MyView: View → 有個要實作 View protocol 的物件 MyView
  • 第二行 var body: some View → 其中的Computed參數 body 是 View protocol 的 associatedtype Body,然而這個 associatedtype 也是一個有去實作 View protocol的物件。
  • 第三行 Text(“Hello, World!”) → 這個 Text 就是那位有去實作 View protocol 用法類似於以前的UILabel的一個物件。

Opaque Types VS Protocol Types

Opaque Types 跟 Protocol Types表面上非常相似。

官方文件表示最大的差異為他們內部所儲存的型態識別,這個結果會影響返回值的型別推斷。

  • Opaque Types 所形容的 return types 是某『一個』由內部所定義的特定具體化的物件,儘管呼叫這個function 或者 parameter的調用者不知道具體內容。
  • Protocol Types 所形容的 return types 可以是任何符合該 protocol 規範的 type。

舉例來說:

protocol Burger{
var bread: Bread
var veg: Vegetable
var cheese: Cheese
var meet: Meet
}
struct McDonaldsBurger{}
struct BurgerKingBurger{}
func getBurger() -> some Burger{...}
func getBurger(by brand: Brand) -> Burger{...}
  • Opaque Types

第一個method是會取得一個 Opaque Type 的 Burger,是哪一家Burger我不會知道,但我知道只會有一家。
在第一個method內部的演算中,不能有因演算結果不同而返回不同家的漢堡。

func getBurger() -> some Burger{ // Compile ERROR
if a > b{
return McDonaldsBurger()
}else {
return BurgerKingBurger()
}
}
  • Protocol Types

第二個Method是會取得任意一個符合 Burger 這個 protocol 的Type。

func getBurger(by brand: Brand) -> Burger{ 
switch brand{
case .McDonald's:
return McDonaldsBurger()
case .BurgerKing:
return BurgerKingBurger()
}
}

這個特性會衍生出另一個問題

由於 Protocol return types 是個不特定 Type,會造成其最後的返回值少了很多特定的參數資訊,這會使得許多要參考這些參數資訊的運算方法(Operations)會無法使用。

let burgerA = getBurger(by .McDonald's)
let burgerB = getBurger(by .McDonald's)
if burgerA == burgerB{...} // Compile Error

會造成這個==計算 Compile 錯誤會有幾個原因:

首先是 Burger 這個 protocol 沒有繼承 Equatable

再來是McDonaldsBurger這個 structure 沒有實作以下Operation

 static func == (lhs: CarToy, rhs: CarToy) -> Bool

最後最後,就算你解決上述兩個問題,最後也會因為 getBurger(by branch: Branch) 返回的值不是一個具體特定的Type而編譯錯誤。

另外點進 Equatable 的文件去看會發現,”==”這個 Operation function 的 input 是兩個 Self,也就是說要拿來比較的兩個值必須是一個被初始化具體的值,但是 protocol return types 的 return value 在型別推斷上會因為其特性而沒辦法肯定他回應的值就是具體化的 type。

結論

  • some 是用來標示 Opaque Types 的關鍵字。
  • Opaque Types 是用來隱藏不必要的資訊給外部調用者。
  • Opaque Types 的返回值是一個由內部定義的具體化 Type 。
  • Protocol return types 與 Opaque return types最大的不同是前者的response不會是一個特定受具體化的 Type 。

出社會之後就沒再更新 Medium了,要說是為什麼了?
摁……下班都快去了,哪還有時間經營 Medium。

但因為2021台灣疫情關係,成天在家裡蹲,多了很多時間可以來接觸被我擱置很久的功課 — SwiftUI。

SwiftUI 是2019就被發表的UI Framework,最低版本支援 iOS 13,在當時還屬於太新的版本,要實現在產品上還有點言之過早。

但隨著時間推進,iOS版本也來到14.4,2021的 WWDC 也即將展開,感覺也是時候可以踏進這個全新的Layout實作的領域。

初步學習了 SwiftUI 的基礎之後,不得不讚嘆這個 SwiftUI 的強大,寫起來輕巧好上手、在 Xcode的介面操作上也非常友善,讓一開始對這個新課題有點排斥的我,馬上就被他的語法所吸引。

感謝你的閱讀,如有錯誤請在下方留言糾正~

參考文件:

--

--

ChunYi LI
One Two Swift

Hi this is Chunyi-Li from Taiwan, a junior iOS deveoper