Swift進階03: 值類型 & 引用類型

值類型

值類型是一種當它被指定到常量或者變量,或者被傳遞給函數(shù)時會被拷貝的類型。
Swift 中所有的基本類型——整數(shù)浮點數(shù)布爾量字符串數(shù)組字典——都是值類型,并且都以結(jié)構(gòu)體的形式在后臺實現(xiàn)。
Swift 中所有的結(jié)構(gòu)體枚舉都是值類型

內(nèi)存的五大分區(qū)

  • Stack (局部變量 & 調(diào)用上下文),系統(tǒng)管理的,是連續(xù)的內(nèi)存空間
  • Heap (new & malloc),程序員管理,類似于鏈表
  • 全局區(qū)
  • 字符區(qū)(字符串 & 常量)
  • __text(指令,即代碼段)

內(nèi)存地址

  • 棧區(qū)的地址 比 堆區(qū)的地址 大
  • 棧是從高地址->低地址,向下延伸,由系統(tǒng)自動管理,是一片連續(xù)的內(nèi)存空間
  • 堆是從低地址->高地址,向上延伸,由程序員管理,堆空間結(jié)構(gòu)類似于鏈表,是不連續(xù)的
  • 日常開發(fā)中的溢出是指堆棧溢出,可以理解為棧區(qū)與堆區(qū)邊界碰撞的情況
  • 全局區(qū)、常量區(qū)都存儲在Mach-O中的__TEXT cString

什么是值類型

通過下列代碼來分析值類型

func test(){
    //棧區(qū)聲明一個地址,用來存儲age變量
    var age = 18
    //傳遞的值
    var age2 = age
    //age、age2是修改獨立內(nèi)存中的值
    age = 30
    age2 = 45
    
    print("age=\(age),age2=\(age2)")
}
test()

從例子中可以得出,age存儲在棧區(qū)

  • 查看age的內(nèi)存情況,從圖中可以看出,棧區(qū)直接存儲的是
    • 獲取age的棧區(qū)地址:po withUnsafePointer(to: &age) { print($0) }
    • 查看age的內(nèi)存情況:x/8g 0x00007ffeefbff480
值類型
  • 查看age2的情況,從下圖中可以看出,age2的賦值相當于將age中的值拿出來,賦值給了age2。其中ageage2 的地址 相差了8字節(jié),從這里可以說明棧空間是連續(xù)的、且是從高到低的
值類型

從上面可以得出,age就是值類型
值類型的特點

  • 地址中存儲的是
  • 值類型的傳遞過程中,相當于傳遞了一個副本,也就是所謂的深拷貝
  • 值傳遞過程中,并不共享狀態(tài)

結(jié)構(gòu)體

結(jié)構(gòu)體的初始化

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

// ***** 未定義init方法 *****
struct HTTeacher {
    var age: Int = 18
    func teacher() {
        print("teacher")
    }
}

// ***** 自定義init方法 *****
struct HTTeacher {
    var age: Int = 18
    func teacher() {
        print("teacher")
    }
    init(age: Int) {
        self.age = age
    }
}

var t = HTTeacher(age: 20)
  • 在結(jié)構(gòu)體中,如果不給屬性默認值,編譯是不會報錯的。即在結(jié)構(gòu)體的定義中,屬性可以賦值,也可以不賦值
  • init方法可以重寫,也可以使用系統(tǒng)默認的

結(jié)構(gòu)體的SIL分析

  • 如果沒有自定義init方法,系統(tǒng)會提供默認的初始化方法
    默認初始化方法
  • 如果提供了自定義的init方法,就只有自定義的方法
    自定義初始化方法

結(jié)構(gòu)體是值類型

定義一個結(jié)構(gòu)體,并進行分析

struct HTTeacher {
    var age: Int = 18
    var age2: Int = 20
}
var t = HTTeacher()
print("end")
  • 通過po t發(fā)現(xiàn),打印結(jié)果就是值,沒有任何與地址有關(guān)的信息
結(jié)構(gòu)體
  • 獲取t的內(nèi)存地址,并查看其內(nèi)存情況

    • 獲取t的地址:po withUnsafePointer(to: &t) { print($0) }
    • 查看內(nèi)存情況:x/8g 0x0000000100008178
    結(jié)構(gòu)體

問題:此時將t賦值給t1,如果修改了t1,t會發(fā)生改變嗎?

t2 被賦予 t 的當前值,存儲在 t中的值就被拷貝給了新的 t2實例。這最終的結(jié)果是兩個完全不同的實例,它們只是碰巧包含了相同的數(shù)字值。由于它們是完全不同的實例, t2age被設(shè)置 30并不影響 tage存儲的值。

結(jié)構(gòu)體

SIL分析

生成SIL文件swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
SIL文件中,我們查看結(jié)構(gòu)體的初始化方法,可以發(fā)現(xiàn)只有init,而沒有malloc,在其中看不到任何關(guān)于堆區(qū)的分配

結(jié)構(gòu)體

總結(jié)

  • 結(jié)構(gòu)體是值類型,且結(jié)構(gòu)體的地址就是第一個成員的內(nèi)存地址
  • 值類型
    • 在內(nèi)存中直接存儲值
    • 值類型的賦值,是一個值傳遞的過程,即相當于拷貝了一個副本,存入不同的內(nèi)存空間,兩個空間彼此間并不共享狀態(tài)
    • 值傳遞其實就是深拷貝

mutating 方法異變

結(jié)構(gòu)體枚舉值類型。默認情況下,值類型屬性不能被自身的實例方法修改。

image

  • 修改push方法如下,查看SIL文件swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil

sil

從上圖中可以看出,push函數(shù)除了item,還有一個默認參數(shù)self,這兩個參數(shù)都是let類型,是不允許修改的

  • 如果你需要在特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以選擇將這個方法異變
  • 你可以選擇在 func關(guān)鍵字前放一個 mutating關(guān)鍵字來使用這個行為
struct HTStack {
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
}

查看SIL文件,找到 push函數(shù),發(fā)現(xiàn)與之前有所不同。添加 mutating后,本質(zhì)是給值類型函數(shù)添加了 inout關(guān)鍵字,相當于在值傳遞過程中,傳遞的是引用(即地址)

sil

inout關(guān)鍵字

一般情況下,在函數(shù)、方法的聲明中,參數(shù)都是默認不可變的,如果想要直接修改,需要給參數(shù)加上 inout關(guān)鍵字。

  • 未加 inout關(guān)鍵字,給參數(shù)賦值,編譯報錯
未加inout
  • 添加 inout關(guān)鍵字,可以給參數(shù)賦值,調(diào)用的時候需要加上 &
inout關(guān)鍵字

總結(jié)

  • 結(jié)構(gòu)體中的函數(shù)如果想修改其中的屬性,需要在函數(shù)前加上 mutating,而類則不用
  • mutating的本質(zhì)是給 self加了 inout關(guān)鍵字
  • inout相當于取地址,可以理解為地址傳遞,即引用
  • mutating修飾方法inout修飾參數(shù)

引用類型-類

類的定義

struct HTTeahcer {
    var age: Int = 18
    var age2: Int = 20
}

class HTTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}

//結(jié)構(gòu)體:值類型
var t = HTTeahcer()
// 類: 引用類型
// t1 存儲在全局區(qū)
var t1 = HTTeacher1()

打印t、t1, 從下圖中可以發(fā)現(xiàn),t中存儲的是值,t1內(nèi)存空間中存儲的是地址

值類型&引用類型

獲取t1變量的地址,并查看內(nèi)存情況

  • 獲取t1的指針地址:po withUnsafePointer(to: &t1) { print($0) }
  • 查看t1全局區(qū)內(nèi)存地址內(nèi)存情況:x/8g 0x0000000100008368
  • 查看t1地址中存儲的堆區(qū)地址內(nèi)存情況: x/8g 0x00000001004425d0
引用類型

引用類型的特點

  • 1、地址中存儲的是 堆區(qū)地址
  • 2、堆區(qū)地址中存儲的是

問題1:此時將t1賦值給t2,如果修改了t2,會導(dǎo)致t1修改嗎?

  • 通過lldb調(diào)試得知,修改t2的值,會導(dǎo)致t1改變。主要是因為 t1t2地址中存儲的是同一個堆區(qū)地址,如果修改,修改的是同一個堆區(qū)地址,所以修改t2會導(dǎo)致t1一起修改,即淺拷貝
引用類型

問題2:如果結(jié)構(gòu)體中包含類對象,此時如果修改t1中的實例對象屬性,t會改變嗎?
代碼如下:

struct HTTeahcer {
    var age: Int = 18
    var age2: Int = 20
    var teacher: HTTeacher1 = HTTeacher1()
}
class HTTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}

var t = HTTeahcer()

var t1 = t
t1.teacher.age = 30

print(t.teacher.age)    // 30
print(t1.teacher.age)   // 30

雖然在結(jié)構(gòu)體中是值傳遞,但是對于teacher,由于是引用類型,所以傳遞的依然是地址

注意:在編寫代碼過程中,應(yīng)該盡量避免值類型包含引用類型

SIL分析

查看當前的SIL文件,盡管HTTeacher1是放在值類型中的,在傳遞的過程中,不管是傳遞還是賦值,teacher都是按照引用計數(shù)開始管理的

SIL

通過po CFGetRetainCount(t.teacher)查看,teacher的引用計數(shù)為3

SIL分析

主要是因為:

  • main中retain一次
  • teacher.getter方法中retain一次
  • teacher.setter方法中retain一次
SIL分析

總結(jié)

通過上述 LLDB查看結(jié)構(gòu)體和類的內(nèi)存模型,有以下結(jié)論:

  • 值類型,相當于一個本地Excel,當我們給你一個 excel文件時,就相當于一個值類型傳遞,你修改了什么我們時不知道的
  • 引用類型,相當于一個在線表格,當我們和你共同編輯一個在線表格時,就相當于一個引用類型傳遞,兩邊都會看到修改的內(nèi)容
  • 結(jié)構(gòu)體方法修改屬性,需要在方法前添加 mutating關(guān)鍵字,本質(zhì)是給函數(shù)的默認參數(shù) self添加 inout關(guān)鍵字,將 selflet變量變成 var變量
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

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