Swift Structure 的兩三事

WuFeng Chiang
Nov 1 · 6 min read

以下做為個人筆記,將 Swift 的 Struct 做一些分解動作。

Structure 沒有繼承 (inheritance)

圖一:試試看
圖二:果然不行

但是 Structure 有多型 (polymorphism)

圖三:不論是成年人或青年少都可以做一位有禮貌的人。

Structure 是 call-by-value的 (傳值呼叫,被傳遞的是"複本",非"原件")

圖四:兩個 instance 的記憶體位址不同,代表它們不是同一份。

(註:Class 是 call-by-reference的)

圖五:兩個 instance 的記憶體位址相同

Structure 是 "不變的"

圖六:兩個 ironMan 的記憶體位址相同,所以改變結構的 property (這裡的 name) 不會影響結構?結構的 property 是可以變的?
圖七:這個例子用編譯器的錯誤訊息來推論:要改變結構的實體 (這裡的 ironMan) 特性 (這裡的 name) 時,會引發結構實體(ironMan) 的改變。所以一旦把 ironMan 用 let 宣告後,會引發 error。
圖八

這裡在 Main 類別裡利用 Swift 的 property observers 語法:didSet {} 來觀察 person 被賦值的情形。

由印出結果看得出來:不論是 person 本身或是其特性 (name) 被賦值時,對 main 來說,其實 person 這個特性的值是一直被取代替換的新值。

結論:Structure 可以被取代,但是它本身就是 "不變的"。


Structure 生來不可變,就算它自己想變也不行!Swift 提供了 mutating 關鍵字讓一切變簡單一些。

下圖展示了 Person 結構自己的 setName() function 嚐試改變 name 特性:

圖九:Person 結構裡,試圖改變自己的 name,引發錯誤:’self’ is immutable。編譯器是說:對 setName() 這個 function 來說,它自己的 instance 本身 (self) 是不能變的!所以要改變 name 還是不行。

依 Structure 的不變性,若要改變某個 Person 實例的 name 特性,只能回傳一個全新的實例。如下圖

圖十:設計 getPersonWith(name:) 來得到有新名字的 Person 實例。

不過在圖九中最後的訊息中,編譯器有提供另一個實作選擇的提示:在 func 前面加上 mutating 這個關鍵字,就可以使 ‘self’ 成為可變 (make ‘self’ mutable)。

圖十一

將 setName() 這個 function 前綴 ‘mutating’ 後,編譯器就能正常功作了。

此外,這裡將 name 前面前綴了 private ,讓 name 的設定只能透過 setName()。(只是為了強化只能透過 setName() 賦值的語意,這個步驟不是必要的)

下方的 name 賦值方式因應改變,但是注意:每次 name 的變更還是一樣會造成 main 裡面的 person 不斷地被重新賦值,這個 "不變的" 特性就是不會變。


這裡進行另一個討論:

圖十二

這裡要討論的是:第 29、30 行的輸出為何?

關鍵在於第 26、27 兩行設定 name 的方式,是等價的嗎?

答案是:不同。

第29行會印出「Tony」;

第30行會印出「Tony Stark」。

原因是在第26行執行後,其 ironMan 和第25行的 ironMan 已經是不同一個了。

(此例中,加上了 readName() 讓 name 可以被類別外程式讀取)


SwiftUI 有額外的語法支援: @State 屬性

在 SwiftUI 中,Structure 隨處可見,所以我們無法不夠了解 Structure 這個 "不變性"。從下面這個例子來討論。假設有一個簡單的 UI 如下:

圖十二

這個例子中,整個畫面由一個 Text 元件和一個 Button 元件所構成。

按下「換人彈指!」這個按鈕後,顯示的文字會從「I am inevitable!」變成「I am IronMan!」。

也就是說,在 Button 被點擊後,Text 顯示的文字要被改變。

對應的程式碼如下:

圖十三

上圖中,Xcode 提示出了一個錯誤訊息,指出 message 這個特性因為是被宣告在 ContentView 這個 Structure 裡,所以若要改變它,也會遇到 "不變性" 所產生的提示。

依前文所言,若希望 message 可變,可以提供一個前綴 mutating 的設值 function 來改變 message;但是,這裡的 self.message 的值同時也是 body 這個 property 的一部分,而 body 也是不可變的。會產生:Cannot use mutating member on immutable value: ‘self’ is immutable 的錯誤。(這裡的 immutable value 指的是 body)

此外,就算能改變 body 裡面的 message 值,要怎麼通知畫面重繪 (因為 body 改變了)呢?

結論是:這裡不是使用 Swift 提供的 mutating 機制,而是使用 SwiftUI 提供的 @State 這個 attribute。

圖十四:第8行中,加入了 @State 這個前綴,讓 message 變成具備「可變」而且「一旦改變便會重繪 View 內容」兩項特性。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade