Swift 中 inout 參數的底層並非簡單的“傳引用”,而是採用**“傳值+拷貝回寫”(Copy-In Copy-Out)**的機制(也稱為“寫時複製”的變種),結合編譯器優化實現高效的參數修改邏輯。以下是其底層原理的詳細拆解:

一、核心機制:Copy-In Copy-Out(CICO)

inout 的本質是“先拷貝參數值到函數棧,函數修改副本後,再將修改後的副本寫回原始變量”,具體流程為:

  1. Copy-In(入參拷貝)
    調用函數時,Swift 會將傳入的變量值拷貝一份到函數的參數棧空間中(類似普通值參數的傳遞)。此時函數內操作的是這個“副本”,而非原始變量。
  2. 函數內修改
    函數執行過程中,對 inout 參數的修改僅作用於棧中的副本。
  3. Copy-Out(出參回寫)
    函數執行結束時,Swift 會將修改後的副本值寫回原始變量的內存地址,從而實現“修改原始值”的效果。

二、編譯器優化:避免不必要的拷貝

為了提升性能,Swift 編譯器會對 inout 進行優化,減少不必要的拷貝操作,主要包括:

1. 對值類型的優化(如結構體、枚舉)
  • 若傳入的變量是“未逃逸”的(即僅在函數內使用,不被閉包捕獲或存儲),編譯器會直接操作原始變量的內存地址,跳過拷貝步驟,此時行為接近“傳引用”。
  • 若變量被逃逸閉包捕獲(如異步操作),則必須執行完整的 Copy-In Copy-Out,避免原始變量在函數外被修改導致數據不一致。
2. 對引用類型的優化(如類)

類的實例本身是“引用語義”,存儲的是指向堆內存的指針。此時 inout 的作用是修改指針本身(而非實例屬性):

  • 若僅修改類實例的屬性,無需 inout(因為指針指向的堆內存可直接修改);
  • 若需替換整個實例(如 p = Person()),inout 會拷貝指針副本,函數結束後將新指針寫回原始變量。

三、與“傳引用”的本質區別

很多開發者會將 inout 誤認為“傳引用”,但二者有根本差異:

特性

inout(Copy-In Copy-Out)

真正的傳引用(如 C++ 指針/引用)

內存操作

函數內操作副本,結束後回寫原始值

直接操作原始變量的內存地址

逃逸場景

必須拷貝,避免數據競爭

可能導致懸垂引用(野指針)

值類型/引用類型適配

統一處理值類型和引用類型

僅針對引用類型有效

四、特殊場景的底層行為

1. 數組/字符串等“可變值類型”

數組、字符串等類型採用“寫時複製(COW)”機制,與 inout 結合時:

  • 若函數內修改 inout 數組,且數組未被其他變量引用,COW 會直接修改原始內存,inout 的回寫操作無額外開銷;
  • 若數組已被多變量引用,COW 會先拷貝一份新數組,函數結束後回寫新數組的指針。
2. 嵌套 inout(如函數返回 inout

Swift 支持函數返回 inout 類型(如 subscript 的 setter),此時底層會返回原始變量的內存地址,而非副本,確保直接修改原始值。示例:

struct Container { var value: Int }
var container = Container(value: 5)

func getInoutValue(_ c: inout Container) -> inout Int {
    return &c.value // 返回原始值的內存地址
}

getInoutValue(&container) += 10
print(container.value) // 輸出:15

五、總結

inout 的底層是**“Copy-In Copy-Out”機制 + 編譯器優化**:

  • 語法上表現為“可修改原始變量”,但本質是值傳遞的增強;
  • 編譯器通過優化減少拷貝,平衡了值類型的安全性和引用類型的靈活性;
  • 與真正的傳引用不同,inout 避免了指針操作的風險,符合 Swift 的安全設計理念。

這種機制既保證了值類型的獨立性(函數內修改不影響原始值直到回寫),又通過優化提升了性能,是 Swift 語言設計中“安全與效率平衡”的典型體現。