Swift進階雜談3:引用類型

  • 類的經常使用寫法
//寫法一
class PDTeacher {
    var age:Int
    func teach() {
        print("teach")
    }
    init(_ age: Int) {
        self.age = age
    }
    
}
var t = PDTeacher.init(20)

//寫法二
class PDTeacher2 {
    var age:Int?;
    func teach() {
        print("teach")
    }
    
    init(_ age: Int) {
        self.age = age
    }
}
var t1 = PDTeacher2.init(12)
複製代碼
  • 在類中,若是屬性沒有賦值,也不是可選項,編譯時會報錯,須要本身實現init方法

爲何類時引用類型?

定義一個類,經過一個例子說明數組

class CJLTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}
var t1 = CJLTeacher1()
複製代碼

類初始化的對象t1,存儲在全局區markdown

  • 打印t一、t:po t1,從圖中能夠看出,t1內存空間中存放的是地址t中存儲的是

  • 獲取t1變量的地址,並查看其內存狀況
  • 獲取t1指針地址:po withUnsafePointer(to: &t1){print($0)}
  • 查看t1全局區地址內存狀況:x/8g 0x0000000100008218
  • 查看t1地址中存儲的堆區地址內存狀況:x/8g 0x00000001040088f0

引用類型 特色

  • 一、地址中存儲的是堆區地址
  • 二、堆區地址中存儲的是

問題1:此時將t1賦值給t2,若是修改了t2,會致使t1修改嗎?

  • 經過lldb調試得知,修改了t2,會致使t1改變,主要是由於t2t1地址中都存儲的是 同一個堆區地址,若是修改,修改是同一個堆區地址,因此修改t2會致使t1一塊兒修改,即淺拷貝

問題2:若是結構體中包含類對象,此時若是修改t1中的實例對象屬性,t會改變嗎?

代碼以下所示app

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

struct CJLTeacher {
    var age: Int = 18
    var age2: Int = 20
    var teacher: CJLTeacher1 = CJLTeacher1()
}

var  t = CJLTeacher()

var t1 = t
t1.teacher.age = 30

//分別打印t1和t中teacher.age,結果以下
t1.teacher.age = 30 
t.teacher.age = 30
複製代碼

從打印結果中能夠看出,若是修改t1中的實例對象屬性,會致使t中實例對象屬性的改變。雖然在結構體中是值傳遞,可是對於teacher,因爲是引用類型,因此傳遞的依然是地址函數

一樣能夠經過lldb調試驗證spa

  • 打印t的地址:po withUnsafePointer(to: &t){print($0)}
  • 打印t的內存狀況: x/8g 0x0000000100008238
  • 打印t中teacher地址的內存狀況:x/8g 0x000000010070e4a0

注意 在編寫代碼過程當中,應該儘可能避免值類型包含引用類型3d

查看當前的SIL文件,儘管CJLTeacher1是放在值類型中的,在傳遞的過程當中,不論是傳遞仍是賦值,teacher都是按照引用計數進行管理的 能夠經過打印teacher的引用計數來驗證咱們的說法,其中teacher的引用計數爲3 指針

主要是是由於:調試

  • mainretain一次excel

  • teacher.getter方法中retain一次code

  • teacher.setter方法中retain一次

mutating

經過結構體定義一個,主要有pushpop方法,此時咱們須要動態修改棧中的數組

  • 若是是如下這種寫法,會直接報錯,緣由是值類型自己是不容許修改屬性

  • 將push方法改爲下面的方式,查看SIL文件中的push函數
struct CJLStack {
    var items: [Int] = []
    func push(_ item: Int){
        print(item)
    }
}
複製代碼

從圖中能夠看出,push函數除了item,還有一個默認參數self,self是let類型,表示不容許修改

  • 嘗試1:若是將push函數修改爲下面這樣,能夠添加進去嗎?
struct CJLStack {
    var items: [Int] = []
    func push(_ item: Int){
        var s = self
        s.items.append(item)
    }
}
複製代碼

打印結果以下 能夠得出上面的代碼並不能將item添加進去,由於s是另外一個結構體對象,至關於值拷貝,此時調用push是將item添加到s的數組中了

  • 根據前文中的錯誤提示,給push添加mutating,發現能夠添加到數組了
struct CJLStack {
    var items: [Int] = []
    mutating func push(_ item: Int){
        items.append(item)
    }
}
複製代碼

查看其SIL文件,找到push函數,發現與以前有所不一樣,push添加mutating(只用於值類型)後,本質上是給值類型函數添加了inout關鍵字,至關於在值傳遞的過程當中,傳遞的是引用(即地址)

inout關鍵字

  • 通常狀況下,在函數的聲明中,默認的參數都是不可變的,若是想要直接修改,須要給參數加上inout關鍵字
  • 未加inout關鍵字,給參數賦值,編譯報錯

  • 添加inout關鍵字,能夠給參數賦值

總結

  • 一、結構體中的函數若是想修改其中的屬性,須要在函數前加上mutating,而類則不用

  • 二、mutating本質也是加一個 inout修飾的self

  • 三、Inout至關於取地址,能夠理解爲地址傳遞,即引用

  • 四、mutating修飾方法,而inout 修飾參數

總結

經過上述LLDB查看結構體 & 類的內存模型,有如下總結:

  • 值類型,至關於一個本地excel,當咱們經過QQ傳給你一個excel時,就至關於一個值類型,你修改了什麼咱們這邊是不知道的

  • 引用類型,至關於一個在線表格,當咱們和你共同編輯一個在先表格時,就至關於一個引用類型,兩邊都會看到修改的內容

  • 結構體中函數修改屬性, 須要在函數前添加mutating關鍵字,本質是給函數的默認參數self添加了inout關鍵字,將self從let常量改爲了var變量

相關文章
相關標籤/搜索