Swift值類型&引用類型

Swift值類型&引用類型

前言

值類型和引用類型是Swift中兩種數據存儲方式,簡單來說值類型就是直接存儲的值,引用類型就是存儲的指針,在談值類型和引用類型前可能你需要了解一些關于內存和Mach-O的知識。下面放上我以前寫過的幾篇文章,僅供參考。

iOS內存五大區
iOS 中的虛擬內存和物理內存
Mach-O探索

簡單來說值類型可以理解為存儲在棧區或者全局區,引用類型一般存儲在堆區,下面我們來看個簡單的例子。

16092321744318.jpg

我們可以看到at的地址都是在棧區,因為棧區通常都是0x7開頭。但是a中存儲的直接就是18這個值,t中存儲的是個全局區的指針。這就是最簡單的值類型和引用類型的區別。

1. 值類型

值類型,即每個實例保持一份數據拷貝。

Swift 中,structenum,以及 tuple 都是值類型。而平時使用的 IntDoubleFloatStringArrayDictionarySet 其實都是用結構體實現的,也是值類型。

Swift 中,值類型的賦值為深拷貝(Deep Copy),值語義(Value Semantics)即新對象和源對象是獨立的,當改變新對象的屬性,源對象不會受到影響,反之同理。

雖然說IntDoubleFloatStringArrayDictionarySet時使用結構體實現的,所以也是值類型,但是就我個人理解來說,這些作為值類型好像就是那么理所當然的,當然對于很長的String還是會通過存儲指向堆區的指針來實現,當然也會通過TaggedPointer等技術進行優化,這里大體還是和OC相同的,感興趣的可以看看我的另一篇文章iOS Objective-C 內存管理。說了這么多,其實我們糾結的一個問題就是struct為什么是值類型,下面我們就來探索一番。

1.1 struct 為什么是值類型

1.1.1 結構體和類的區別

從代碼看區別

class CTeacher {
    var age: Int?
    var name: String!
    var height: Float = 185.3
}

struct STeacher {
    var age: Int
}

let ct = CTeacher()
ct.name = "testC"

let st1 = STeacher(age: 20)
let st2 = STeacher(age: 21, name: "testS", height: 180.1)

通過以上的代碼我們可以知道:

  1. 類中的屬性需要使用?!或者賦初始值才不會導致編譯報錯
  2. 結構體中的屬性不需要賦初始值,也不用使用?!
  3. 結構體的初始化需要同時初始化結果圖內部的屬性
  4. 類的初始化可以不用初始化類中的屬性
  5. 結構體中的optional屬性,或者賦值的屬性可以不在結構體初始化的時候初始化

從sil代碼看區別

class CTeacher {
  @_hasStorage @_hasInitialValue var age: Int? { get set }
  @_hasStorage @_hasInitialValue var name: String! { get set }
  @_hasStorage @_hasInitialValue var height: Float { get set }
  @objc deinit
  init()
}

struct STeacher {
  @_hasStorage var age: Int { get set }
  @_hasStorage @_hasInitialValue var name: String? { get set }
  @_hasStorage @_hasInitialValue var height: Float { get set }
  init(age: Int, name: String? = nil, height: Float = 185.3)
}

通過sil代碼我們可以看到:

  1. 類中如果不實現自定義init方法就會有個init()方法
  2. 結構體中會提供默認的初始化方法

1.1.2 驗證結構體是值類型

定義一個結構體:

struct Teacher {
    var age: Int
    var age1: Int
}

var t = Teacher(age: 18, age1: 20)

使用lldb調試:

16093081162277.jpg

此時我們可以看到,結構體內部直接存儲的就是結構體中的屬性的值。所以說結構體是值類型是沒問題的。

1.1.3 驗證結構體是值拷貝

此時我們創建個新的實例變量t1,并將t賦值給t1,代碼如下:

struct Teacher {
    var age: Int
    var age1: Int
}

var t = Teacher(age: 18, age1: 20)
var t1 = t
t1.age = 22

print("end")
16093157152789.jpg

在修改t1的值后我們發現t中的數據并沒有改變,所以說tt1之間是值傳遞,即tt1是存儲在不同內存空間的,在var t1 = t時,是將t中的值,拷貝到t1中,t1修改時,只會修改自己內存中的數據,是不會影響到t的內存的。

另外在打印兩個實例變量地址的時候也明顯不是一樣的。

1.1.4 通過sil驗證struct是值類型

我們查看Teacherinit方法:

// Teacher.init(age:age1:)
sil hidden @main.Teacher.init(age: Swift.Int, age1: Swift.Int) -> main.Teacher : $@convention(method) (Int, Int, @thin Teacher.Type) -> Teacher {
// %0 "$implicit_value"                           // user: %3
// %1 "$implicit_value"                           // user: %3
// %2 "$metatype"
bb0(%0 : $Int, %1 : $Int, %2 : $@thin Teacher.Type):
  %3 = struct $Teacher (%0 : $Int, %1 : $Int)     // user: %4
  return %3 : $Teacher                            // id: %4
} // end sil function 'main.Teacher.init(age: Swift.Int, age1: Swift.Int) -> main.Teacher'

我們可以看到init方法中并沒有調用malloc相關的開辟內存的方法,這里也是只是將傳入的兩個值賦給初始化的結構體而已。

1.1.5 常量值類型

如果聲明一個值類型的常量,那么就意味著該常量是不可變的(無論內部數據為 var還是let)。

16093933472434.jpg

1.1.6 小結

至此我們就驗證了結構體是值類型:

  1. 結構體不像類一樣需要調用malloc等方法去開辟內存空間
  2. 結構體的內存中直接存儲值
  3. 值類型的賦值是一個值傳遞的過程,相當于深拷貝

1.2 其他

關于enumtuple這里就不一一分析了,在后續的篇章中會陸續提到。

2. 引用類型

引用類型,即所有實例共享一份數據拷貝。

Swift 中,classclosure是引用類型。引用類型的賦值是淺拷貝(Shallow Copy),引用語義(Reference Semantics)即新對象和源對象的變量名不同,但其引用(指向的內存空間)是一樣的,因此當使用新對象操作其內部數據時,源對象的內部數據也會受到影響。

2.1 驗證類是引用類型

定義一個類

class Teacher {
    var age: Int = 28
    var age1: Int = 20
}

var t = Teacher()

print("end")

lldb調試

16093223424561.jpg

從lldb調試中我們可以看到,類實例對象指針內部存儲的是一個指向全局區的指針,而這塊內存區域才是存儲的真正的實例變量的信息,所以說類是個引用類型。

2.2 驗證類對象是指針拷貝

我們使用如下代碼進行驗證:

class Teacher {
    var age: Int = 28
    var name: String = "teacher1"
}

var t = Teacher()
print(t.age)
var t1 = t
t1.age = 18

print(t.age)

print("end")
16093943159284.jpg

通過打印結果我們可以知道,雖然我們修改的是t1這個實例對象中age的值,但是當我們打印t這個實例變量的age的值的時候也隨之改變了,所以我們就能夠確定類對象之間是指針拷貝,并且在內存地址的打印中我們也可以清晰的看見,它們指向同一片內存空間,一個改變則全部都改變。

2.4 通過sil進一步驗證類的引用類型

其實到這里也就沒什么好說的的了,在類的初始化的時候肯定是會調用alloc方法來開辟內存空間的,這里借著上面的sil代碼,我們來看看Info這個類的Info.__allocating_init()方法吧:

16093970325654.jpg

這里首先就調用了alloc_refInfo初始化一塊內存空間。

2.5 常量引用類型

如果聲明一個引用類型的常量,那么就意味著該常量的引用不能改變(即不能被同類型變量賦值),但指向的內存中所存儲的變量是可以改變的,示例如下:

16098103813847.jpg

此處是不會報編譯錯誤的,這點與值類型也是不同的。

2.6 小結

至此我們就驗證了類是引用類型:

  1. 類需要調用alloc等方法去開辟內存空間
  2. 類的實例對象中存儲的是指針地址,這個地址中存儲的才是值
  3. 類的實例對象的賦值是一個指針拷貝的過程,相當于淺拷貝

3. 嵌套類型

所謂嵌套類型就是引用類型中有值類型,或者值類型中有引用類型,其實在上面的例子中已經涉及到了,下面我們通過兩兩組合,分四種情況來簡單介紹一下。

3.1 值類型嵌套引用類型

這里是在結構體中添加一個引用類型的屬性,示例代碼如下:

class Info {
    var height: Int = 185
    var weight: Double = 60.5
}

struct Teacher {
    var age: Int = 18
    var name: String = "teacher1"
    var info: Info = Info()
}

var t = Teacher()
print(t.info.weight)

var t1 = t
t1.info.weight = 80

print(t.info.weight)
print(t1.info.weight)

print("end")
16093958789319.jpg

我們可以看到,在值類型中使用引用類型:

  1. 隨著t1.info.weight的改變,t中的也改變了
  2. 所以說依舊是值拷貝,只不過是拷貝了引用類型數據的指針
  3. 這里的值傳遞是只傳遞了指針

那么真的這個引用類型會不會涉及到內存引用計數的管理呢?其實答案是肯定的,下面我們通過sil代碼驗證一下:

16093964609674.jpg

通過sil代碼我們可以看到strong_retainstrong_release的調用,所以說在值類型的內部使用引用類型依舊是需要通過引用計數管理的。

所以說,應該盡量避免這種值類型中使用引用類型的寫法,因為值類型的初衷就是為了不使用指針指向另一片內存區域,從而減少內存的使用,以提升效率。

3.2 值類型嵌套值類型

其實,在上面我們已經介紹過了,在SwiftInt的底層實現就是個結構體,所以也是值類型。

struct Teacher {
    var age: Int = 18
}

值類型嵌套值類型:

  • 在賦值的時候創建新的變量,兩者是獨立的。
  • 嵌套的值類型變量也會創建新的變量,也可以說是深拷貝一份變量的值

3.3 引用類型嵌套引用類型

其實這也是我們經常用到的一種嵌套,比如類中嵌套類。

class Info {
    var height: Int = 185
    var weight: Double = 60.5
}

class Teacher {
    var age: Int = 18
    var name: String = "teacher1"
    var info: Info = Info()
}

引用類型嵌套引用類型:

  • 引用類型再賦值時創建了新的變量
  • 新變量和源變量指向同一塊內存,內部引用類型變量也指向同一塊內存地址
  • 改變引用類型嵌套的引用類型的值,也會影響到其他變量的值。

3.4 引用類型嵌套值類型

這個在上面我們也用到過,類中的Int類型的屬性就是很好的例子。

class Teacher {
    var age: Int = 28
}

引用類型嵌套值類型時:

  • 賦值時創建了新的變量
  • 新變量和源變量指向同一塊內存
  • 改變源變量的內部值,會影響到其他變量的值
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容