Swift 程式語言

程式語言基礎:認清 Swift 數值型別 (Value Type) 與參考型別 (Reference Type) 的差別

在 Swift,數值型別 (Value Type) 與參考型別 (Reference Type) 到底有甚麼差異呢?它們又是如何被儲存到記憶體呢?如果你還不清楚,這篇教學就不容錯過了!我們將會以簡單易明的例子解答這些疑問,為你打好程式語言的基礎。
程式語言基礎:認清 Swift 數值型別 (Value Type) 與參考型別 (Reference Type) 的差別
程式語言基礎:認清 Swift 數值型別 (Value Type) 與參考型別 (Reference Type) 的差別
In: Swift 程式語言
本篇原文 (標題:Difference between value type and a reference type in iOS swift?) 刊登於作者 Medium,由 Abhilash KM 所著,並授權翻譯及轉載。

數值型別 (Value Type) 與參考型別 (Reference Type) 的差異是所有程式語言的基礎。大部分開發者都可能是從 C 語言開始程式設計生涯。如果你還記得傳值 (Call By Value) 與傳參考 (Call By Reference) 函式的話,那麼你大概知道我的意思。讓我們來看看 Apple 怎麼說。

就如標題所說,Swift 中的型別可以分為兩種類型:

  • 數值型別 ── 每個實例保存資料一份獨立的備份。當這類型別被指派給一個變數或常數、或是被傳送到函式時,就會創建一個新的實例(備份)。

  • 參考型別 ── 每個實例共享資料的單一備份。當這類型別被初始化、被指派給一個變數或常數、或者是被傳送到函式時,就會回傳參考到相同的實例。

看看下面的 GIF 動畫來了解上述定義:

difference between value type and reference type

寫些程式碼吧!

思考一下下面的 Playground 程式碼區塊:

上面的 Home class 並沒有任何初始器,儲存屬性 (Stored Property) roomCount 預設值為 2。現在,看看第一個建立的實例 peterVilla,它有一個數值為 2roomCount 屬性。

現在,如上面程式碼般,建立一個新物件 johnVilla,然後指派先前的物件給它。你認為 johnVillaroomCount 的值會是什麼呢?它會跟 peterVilla 裡的 roomCount 相同吧?沒錯,就是 2。

現在變更 johnVilla 裡的 roomCount 值為 5,並印出兩個物件的 roomCount,你會發現兩個都會印出數字 5

但,為甚麼會這樣呢?

原因是:

Class 是參考型別,它會複製一份參考,然後建立一個共享實例。在複製後,兩個變數會共同參照同一份資料的實例,因此調整第二個變數的資料時,也會影響原本的變數。

附註:Class 是參考型別,也就是說,一個 Class 型別的變數不會儲存實際的實例,但會儲存一個參考到記憶體 (heap) 儲存實例的位置。

問題:如果我們把上面區塊裡程式碼的 var 變成 let 又會怎樣呢?

答案:甚麼事都不會發生。輸入下面的程式碼並執行:

let peterVilla = Home()
let johnVilla = peterVilla

這對輸出不會有影響。在這情況下 roomCount 還是 5。為什麼呢?

因為 Class 都是參考型別物件。letvar 唯一的不同,就是能否重新指派變數到相同型別的不同 Class。letvar 不會影響更動 Class 變數的能力。

不過,看看下面的程式碼:

上面的程式碼已說明了大概。

簡單來想,一旦我們建造了或是買了一個 HOME,並將它送給 let 常數,那麼我們只能更改 roomCount。所以,John 無法升級它的 HOME,因為它是不可更動的。我們無法建立一個新 HOME 或是更改它,否則 let 可是會對我們發火 🤬😡🤬😡 的。我想你現在可以理解了吧!

如何 johnVilla 是 var 的話會怎樣呢?

如果 johnVillavar 的話,那麼它就可更動了。如此一來,John 就可以隨時升級或是更改他的 HOME。看看下面的程式碼:

如果 Home 是 Struct 呢?

思考一下下面的 Playground 程式碼區塊。在這裡,Homestruct

因為這邊的 Homestruct,然後 johnVillalet 常數,所以我們無法像上面的部分般更動 roomCount

這是因為 Struct 是數值型別,而且使用 let 讓這個物件變成了常數,它不能被更動或是重新指派,就連它底下的變數也不行。Struct 如果以 var 來建立,我們就可以更改它的變數。

所以,我們也無法重新指派 johnVilla 的數值。

let peterVilla = Home()
let johnVilla = peterVilla
johnVilla = Home()
//error: cannot assign to value: ‘johnVilla’ is a ‘let’ constant

附註: 所以對數值型別來說,如果我們想要重新指派物件或是更改物件裡的變數,我們應該要宣告它為是可變動的 (‘var’)。

上面的程式碼非常簡單,涵蓋了所有層面,像是重新指派數值以及更改成員變數等。雖然我們在第 44 行裡指派了 peterVillajohnVilla,但 johnVilla 是個獨立實例,所以它自己會有一份 peterVilla 的備份資料。

附註:當你在 Swift 裡變動數值型別時,你不是真的在變動那個數值,而是在變動那個變數所持有的數值。

雖說如此,但在 Swift 裡,struct 不是唯一的 value type,而 class 也不是唯一的 reference type。看看下面的其他例子:

Swift 以 class 代表參考型別,這一點與 Objective-C 十分相似。在 Objective-C 裡,所有繼承於 NSObject 的東西都儲存為參考型別。

我們該如何選擇使用數值型別或是參考型別?

資料來源:Apple 官方文件

所以如果你想要建立一個新型別,你會選擇哪種型別呢?當你使用 Cocoa 時,很多 API 期望收到 NSObject 的 Subclass,所以我們應使用 Class。但在其他情況下,有一些方針可供參考:

在以下情況,我們可以使用數值型別:

  • 以 == 來比較實例的資料較為合理(一個雙等於運算符(==)是用來比較 數值的)
  • 你希望副本有獨立狀態
  • 資料將會被跨越多個執行序的程式碼使用,而你擔心資料會在其他執行序中被變更

在以下情況,我們可以使用參考型別:

  • === 來比較實例較為合理( === 是用來確認兩個物件是否完全相同的,包括儲存資料的記憶體位置。)
  • 你希望創建一個共享、可變動的狀態

數值型別與參考型別如何被儲存到記憶體?

  • 數值型別 ── 儲存於 Stack Memory
  • 參考型別 ── 儲存於 Managed Heap Memory

Stack 與 Heap 的不同之處

如前文所指,參考型別實例是儲存於 heap 的,而數值型別的實例像是 Struct 則是放在記憶體裡一個叫 stack 的區域內。如果數值型別實例是 Class 實例的一部分,那麼數值就會跟 Class 實例一起被儲存在 Heap 裡。

Stack 是用於靜態 (Static) 記憶體配置,而 Heap 則是用於動態 (Dynamic) 記憶體配置,兩者皆儲存在電腦的 RAM 裡。

Stack 是由 CPU 牢牢地優化及管理的。當一個函式創建一個變數時,Stack 就會儲存那個變數,而在在函式消失時,變數就會被銷毀。配置在 Stack 裡的變數是直接儲存在記憶體裡,而且存取這個記憶體非常快速。當函式或方法呼叫另一個函式、而它又呼叫另一個函式時,所有這些函式的執行都會暫停,直到最後一個函式回傳它的數值。Stack 一直都是以 LIFO (Last In First Out,後進先出) 的方式排列,最後進入的區塊總是最先會被釋放的區塊。這樣,追蹤 Stack 就非常簡單,因為只要調整一個指標,就可以從 Stack 裡釋放出一個區塊。因為 Stack 非常有組織,所以它十分有效率。

系統使用 Heap 來儲存由其他物件引用的資料。Heap 大體上是一個大型記憶體池,在此系統可以請求並動態配置記憶體區塊。Heap 並不會像 Stack 般自動銷毀物件,必須靠外部作業才能銷毀物件,Apple 裝置的中 ARC 就是負責這項工作。參考數是由 ARC 所追蹤的,當它變為零時,物件就會被重新分配。因此,整個過程(配置、追蹤參考、以及重新配置)相對 Stack 來說是比較慢的,所以數值型別會比參考型別快。

總結

本次教學就到這樣,希望你從中學到更多知識。如果你喜歡這篇文章,歡迎分享到你的社交平台,讓更多人可以看到這篇教學 👏!

你可以在 Medium 上追蹤我,以獲得最新文章。同時也可在 LinkedInTwitter 上聯絡我。

如果有任何評論、問題或是建議,歡迎在底下留言。

本篇原文 (標題:Difference between value type and a reference type in iOS swift?) 刊登於作者 Medium,由 Abhilash KM 所著,並授權翻譯及轉載。
作者簡介:Abhilash,一名軟件工程師和攝影師,居於印度班加羅爾。從攝影到編程、甚至是旅遊、藝術和設計都十分感興趣。在 Medium 上撰寫有關 iOS 和 Swift 語言的文章,擁有超過 1500 名追蹤者。
LinkedIn : https://www.linkedin.com/in/abhimuralidharan/
Twitter : https://twitter.com/abhilashkm1992
譯者簡介:楊敦凱-目前於科技公司擔任 iOS Developer,工作之餘開發自有 iOS App同時關注網路上有趣的新玩意、話題及科技資訊。平時的興趣則是與自身專業無關的歷史、地理、棒球。來信請寄到:[email protected]
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。