Swift值類型&引用類型

Swift值類型&引用類型

前言

值類型和引用類型是Swift中兩種數(shù)據(jù)存儲(chǔ)方式棍矛,簡(jiǎn)單來說值類型就是直接存儲(chǔ)的值,引用類型就是存儲(chǔ)的指針姻几,在談值類型和引用類型前可能你需要了解一些關(guān)于內(nèi)存和Mach-O的知識(shí)索烹。下面放上我以前寫過的幾篇文章屠列,僅供參考。

iOS內(nèi)存五大區(qū)
iOS 中的虛擬內(nèi)存和物理內(nèi)存
Mach-O探索

簡(jiǎn)單來說值類型可以理解為存儲(chǔ)在棧區(qū)或者全局區(qū)罢艾,引用類型一般存儲(chǔ)在堆區(qū)楣颠,下面我們來看個(gè)簡(jiǎn)單的例子尽纽。

16092321744318.jpg

我們可以看到at的地址都是在棧區(qū),因?yàn)闂^(qū)通常都是0x7開頭童漩。但是a中存儲(chǔ)的直接就是18這個(gè)值弄贿,t中存儲(chǔ)的是個(gè)全局區(qū)的指針。這就是最簡(jiǎn)單的值類型和引用類型的區(qū)別矫膨。

1. 值類型

值類型挎春,即每個(gè)實(shí)例保持一份數(shù)據(jù)拷貝。

Swift 中豆拨,struct直奋,enum,以及 tuple 都是值類型施禾。而平時(shí)使用的 Int脚线、DoubleFloat弥搞、String邮绿、ArrayDictionary攀例、Set 其實(shí)都是用結(jié)構(gòu)體實(shí)現(xiàn)的船逮,也是值類型。

Swift 中粤铭,值類型的賦值為深拷貝(Deep Copy)挖胃,值語義(Value Semantics)即新對(duì)象和源對(duì)象是獨(dú)立的,當(dāng)改變新對(duì)象的屬性梆惯,源對(duì)象不會(huì)受到影響酱鸭,反之同理。

雖然說Int垛吗、Double凹髓、FloatString怯屉、Array蔚舀、DictionarySet時(shí)使用結(jié)構(gòu)體實(shí)現(xiàn)的锨络,所以也是值類型赌躺,但是就我個(gè)人理解來說,這些作為值類型好像就是那么理所當(dāng)然的足删,當(dāng)然對(duì)于很長(zhǎng)的String還是會(huì)通過存儲(chǔ)指向堆區(qū)的指針來實(shí)現(xiàn)寿谴,當(dāng)然也會(huì)通過TaggedPointer等技術(shù)進(jìn)行優(yōu)化,這里大體還是和OC相同的失受,感興趣的可以看看我的另一篇文章iOS Objective-C 內(nèi)存管理讶泰。說了這么多咏瑟,其實(shí)我們糾結(jié)的一個(gè)問題就是struct為什么是值類型,下面我們就來探索一番痪署。

1.1 struct 為什么是值類型

1.1.1 結(jié)構(gòu)體和類的區(qū)別

從代碼看區(qū)別

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. 類中的屬性需要使用?码泞、!或者賦初始值才不會(huì)導(dǎo)致編譯報(bào)錯(cuò)
  2. 結(jié)構(gòu)體中的屬性不需要賦初始值狼犯,也不用使用?余寥、!
  3. 結(jié)構(gòu)體的初始化需要同時(shí)初始化結(jié)果圖內(nèi)部的屬性
  4. 類的初始化可以不用初始化類中的屬性
  5. 結(jié)構(gòu)體中的optional屬性,或者賦值的屬性可以不在結(jié)構(gòu)體初始化的時(shí)候初始化

從sil代碼看區(qū)別

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. 類中如果不實(shí)現(xiàn)自定義init方法就會(huì)有個(gè)init()方法
  2. 結(jié)構(gòu)體中會(huì)提供默認(rèn)的初始化方法

1.1.2 驗(yàn)證結(jié)構(gòu)體是值類型

定義一個(gè)結(jié)構(gòu)體:

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

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

使用lldb調(diào)試:

16093081162277.jpg

此時(shí)我們可以看到悯森,結(jié)構(gòu)體內(nèi)部直接存儲(chǔ)的就是結(jié)構(gòu)體中的屬性的值宋舷。所以說結(jié)構(gòu)體是值類型是沒問題的。

1.1.3 驗(yàn)證結(jié)構(gòu)體是值拷貝

此時(shí)我們創(chuàng)建個(gè)新的實(shí)例變量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的值后我們發(fā)現(xiàn)t中的數(shù)據(jù)并沒有改變,所以說tt1之間是值傳遞幻碱,即tt1是存儲(chǔ)在不同內(nèi)存空間的绎狭,在var t1 = t時(shí),是將t中的值褥傍,拷貝到t1中儡嘶,t1修改時(shí),只會(huì)修改自己內(nèi)存中的數(shù)據(jù)恍风,是不會(huì)影響到t的內(nèi)存的蹦狂。

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

1.1.4 通過sil驗(yàn)證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方法中并沒有調(diào)用malloc相關(guān)的開辟內(nèi)存的方法邻耕,這里也是只是將傳入的兩個(gè)值賦給初始化的結(jié)構(gòu)體而已鸥咖。

1.1.5 常量值類型

如果聲明一個(gè)值類型的常量燕鸽,那么就意味著該常量是不可變的(無論內(nèi)部數(shù)據(jù)為 var還是let)兄世。

16093933472434.jpg

1.1.6 小結(jié)

至此我們就驗(yàn)證了結(jié)構(gòu)體是值類型:

  1. 結(jié)構(gòu)體不像類一樣需要調(diào)用malloc等方法去開辟內(nèi)存空間
  2. 結(jié)構(gòu)體的內(nèi)存中直接存儲(chǔ)值
  3. 值類型的賦值是一個(gè)值傳遞的過程,相當(dāng)于深拷貝

1.2 其他

關(guān)于enumtuple這里就不一一分析了啊研,在后續(xù)的篇章中會(huì)陸續(xù)提到御滩。

2. 引用類型

引用類型,即所有實(shí)例共享一份數(shù)據(jù)拷貝党远。

Swift 中削解,classclosure是引用類型。引用類型的賦值是淺拷貝(Shallow Copy)沟娱,引用語義(Reference Semantics)即新對(duì)象和源對(duì)象的變量名不同氛驮,但其引用(指向的內(nèi)存空間)是一樣的,因此當(dāng)使用新對(duì)象操作其內(nèi)部數(shù)據(jù)時(shí)济似,源對(duì)象的內(nèi)部數(shù)據(jù)也會(huì)受到影響矫废。

2.1 驗(yàn)證類是引用類型

定義一個(gè)類

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

var t = Teacher()

print("end")

lldb調(diào)試

16093223424561.jpg

從lldb調(diào)試中我們可以看到盏缤,類實(shí)例對(duì)象指針內(nèi)部存儲(chǔ)的是一個(gè)指向全局區(qū)的指針,而這塊內(nèi)存區(qū)域才是存儲(chǔ)的真正的實(shí)例變量的信息蓖扑,所以說類是個(gè)引用類型唉铜。

2.2 驗(yàn)證類對(duì)象是指針拷貝

我們使用如下代碼進(jìn)行驗(yàn)證:

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

通過打印結(jié)果我們可以知道,雖然我們修改的是t1這個(gè)實(shí)例對(duì)象中age的值律杠,但是當(dāng)我們打印t這個(gè)實(shí)例變量的age的值的時(shí)候也隨之改變了潭流,所以我們就能夠確定類對(duì)象之間是指針拷貝,并且在內(nèi)存地址的打印中我們也可以清晰的看見柜去,它們指向同一片內(nèi)存空間灰嫉,一個(gè)改變則全部都改變。

2.4 通過sil進(jìn)一步驗(yàn)證類的引用類型

其實(shí)到這里也就沒什么好說的的了嗓奢,在類的初始化的時(shí)候肯定是會(huì)調(diào)用alloc方法來開辟內(nèi)存空間的熬甫,這里借著上面的sil代碼,我們來看看Info這個(gè)類的Info.__allocating_init()方法吧:

16093970325654.jpg

這里首先就調(diào)用了alloc_refInfo初始化一塊內(nèi)存空間蔓罚。

2.5 常量引用類型

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

16098103813847.jpg

此處是不會(huì)報(bào)編譯錯(cuò)誤的郑象,這點(diǎn)與值類型也是不同的。

2.6 小結(jié)

至此我們就驗(yàn)證了類是引用類型:

  1. 類需要調(diào)用alloc等方法去開辟內(nèi)存空間
  2. 類的實(shí)例對(duì)象中存儲(chǔ)的是指針地址茬末,這個(gè)地址中存儲(chǔ)的才是值
  3. 類的實(shí)例對(duì)象的賦值是一個(gè)指針拷貝的過程厂榛,相當(dāng)于淺拷貝

3. 嵌套類型

所謂嵌套類型就是引用類型中有值類型,或者值類型中有引用類型丽惭,其實(shí)在上面的例子中已經(jīng)涉及到了击奶,下面我們通過兩兩組合,分四種情況來簡(jiǎn)單介紹一下责掏。

3.1 值類型嵌套引用類型

這里是在結(jié)構(gòu)體中添加一個(gè)引用類型的屬性柜砾,示例代碼如下:

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. 所以說依舊是值拷貝痰驱,只不過是拷貝了引用類型數(shù)據(jù)的指針
  3. 這里的值傳遞是只傳遞了指針

那么真的這個(gè)引用類型會(huì)不會(huì)涉及到內(nèi)存引用計(jì)數(shù)的管理呢?其實(shí)答案是肯定的瞳浦,下面我們通過sil代碼驗(yàn)證一下:

16093964609674.jpg

通過sil代碼我們可以看到strong_retainstrong_release的調(diào)用担映,所以說在值類型的內(nèi)部使用引用類型依舊是需要通過引用計(jì)數(shù)管理的。

所以說叫潦,應(yīng)該盡量避免這種值類型中使用引用類型的寫法蝇完,因?yàn)橹殿愋偷某踔跃褪菫榱瞬皇褂弥羔樦赶蛄硪黄瑑?nèi)存區(qū)域,從而減少內(nèi)存的使用,以提升效率短蜕。

3.2 值類型嵌套值類型

其實(shí)泛源,在上面我們已經(jīng)介紹過了,在SwiftInt的底層實(shí)現(xiàn)就是個(gè)結(jié)構(gòu)體忿危,所以也是值類型达箍。

struct Teacher {
    var age: Int = 18
}

值類型嵌套值類型:

  • 在賦值的時(shí)候創(chuàng)建新的變量,兩者是獨(dú)立的铺厨。
  • 嵌套的值類型變量也會(huì)創(chuàng)建新的變量缎玫,也可以說是深拷貝一份變量的值

3.3 引用類型嵌套引用類型

其實(shí)這也是我們經(jīng)常用到的一種嵌套,比如類中嵌套類解滓。

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()
}

引用類型嵌套引用類型:

  • 引用類型再賦值時(shí)創(chuàng)建了新的變量
  • 新變量和源變量指向同一塊內(nèi)存赃磨,內(nèi)部引用類型變量也指向同一塊內(nèi)存地址
  • 改變引用類型嵌套的引用類型的值,也會(huì)影響到其他變量的值洼裤。

3.4 引用類型嵌套值類型

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

class Teacher {
    var age: Int = 28
}

引用類型嵌套值類型時(shí):

  • 賦值時(shí)創(chuàng)建了新的變量
  • 新變量和源變量指向同一塊內(nèi)存
  • 改變?cè)醋兞康膬?nèi)部值腮鞍,會(huì)影響到其他變量的值
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末值骇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子移国,更是在濱河造成了極大的恐慌吱瘩,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迹缀,死亡現(xiàn)場(chǎng)離奇詭異使碾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祝懂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門票摇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砚蓬,你說我怎么就攤上這事矢门。” “怎么了怜械?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵颅和,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缕允,道長(zhǎng),這世上最難降的妖魔是什么蹭越? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任障本,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘驾霜。我一直安慰自己案训,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布粪糙。 她就那樣靜靜地躺著强霎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蓉冈。 梳的紋絲不亂的頭發(fā)上城舞,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音寞酿,去河邊找鬼家夺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伐弹,可吹牛的內(nèi)容都是我干的拉馋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼惨好,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼煌茴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起日川,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤景馁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逗鸣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體合住,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年撒璧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了透葛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卿樱,死狀恐怖僚害,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情繁调,我是刑警寧澤萨蚕,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蹄胰,受9級(jí)特大地震影響岳遥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裕寨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一浩蓉、第九天 我趴在偏房一處隱蔽的房頂上張望派继。 院中可真熱鬧,春花似錦捻艳、人聲如沸驾窟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绅络。三九已至,卻和暖如春嘁字,著一層夾襖步出監(jiān)牢的瞬間恩急,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工拳锚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留假栓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓霍掺,卻偏偏與公主長(zhǎng)得像匾荆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杆烁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容