swift-結(jié)構(gòu)體和類

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

在 Swift 標(biāo)準(zhǔn)庫中嚼摩,絕大多數(shù)的公開類型都是結(jié)構(gòu)體钦讳,而枚舉和類只占很小一部分

比如Bool、Int枕面、Double愿卒、 String、Array潮秘、Dictionary等常見類型都是結(jié)構(gòu)體

struct Date { 
 var year: Int
 var month: Int
 var day: Int
 } 
 var date = Date(year: 2019, month: 6, day: 23) 

所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initializer琼开,初始化方法、構(gòu)造器枕荞、構(gòu)造方法)

在第⑥行調(diào)用的柜候,可以傳入所有成員值,用以初始化所有成員(存儲屬性躏精,Stored Property)渣刷。

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

編譯器會根據(jù)情況為所有結(jié)構(gòu)體類型自動生成初始化器(initializer),有可能會生成多個初始化器.生成初始化器的原則是:保證所有成員都有值.

image-20210402134927166
image-20210402135005932

比如:

struct TestStruct{
    var a: Int
    var b: Int
}
// 編譯器自動生成了傳入a,b的初始化器
let s = TestStruct(a: 10, b: 20)

如果結(jié)構(gòu)體的成員有初始值,那么編譯器會給結(jié)構(gòu)體生成多個初始化器.

比如只有一個成員有初始值:

struct TestStruct{
    var a: Int = 0
    var b: Int
}

// 生成了兩個初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)

兩個成員都有初始化器:

struct TestStruct{
    var a: Int = 0
    var b: Int = 0
}

//生成4個初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)
let s3 = TestStruct(a: 10)
let s4 = TestStruct()

自定義初始化器

我們也可以給結(jié)構(gòu)體自定義初始化器,但是一旦自定義初始化器后,編譯器就不會再自動生成初始化器:

img
img

自定義初始化器后,只有自定義的初始化器有效,系統(tǒng)不會再生成初始化器

img

其實我們自定義的初始化器和系統(tǒng)自動生成的初始化器,本質(zhì)是一模一樣的,他們的底層匯編都是一樣的.

結(jié)構(gòu)體內(nèi)存

結(jié)構(gòu)體的內(nèi)存和有關(guān)聯(lián)值的枚舉類型很相似:

func testStruct() {
    struct Point {
        var x = 10
        var y = 20
        var b = true
        
    }
    var p = Point(x: 11, y: 22, b: false)
    p = Point(x: 22)

    print(Mems.memStr(ofVal: &p))
    print(MemoryLayout<Point>.size)
    print(MemoryLayout<Point>.stride)
    print(MemoryLayout<Point>.alignment)
    
}

testStruct()

0x0000000000000016 0x0000000000000014 0x0000000000000001

17

24

8

Class 類

類的定義

類的定義和結(jié)構(gòu)體很相似,但是==如果類的成員沒有初始值,編譯器不會為類生成初始化器==:

image-20210402140735056
  • 如果類的成員都有初始值,編譯器才會為類生成無參初始化器:
  • 成員的初始化是在這個初始化器中完成的
func testClass() {

    class Point {
        var x = 10
        var y = 20
        var b = true
        func test() {
        }
    }
    let p = Point()
    p.test()
}

testClass()

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

從上面可以看到結(jié)構(gòu)體和類真的是非常的相似,那么結(jié)構(gòu)體和類有什么區(qū)別呢?

類型不同,它們的本質(zhì)區(qū)別是:結(jié)構(gòu)體是值類型,類是引用類型

image-20210402141717033
//類
func test(){
class Person{
    var name:String
    var age: Int
    
    init(){
        self.name = "Jack"
        self.age = 18
    }
  }

let person = Person()

//結(jié)構(gòu)體
struct Point {
    let x: Int
    let y: Int
  }
let point = Point(x: 10, y: 20)
}

如上所示的代碼,因為point 和 person都是在test()函數(shù)內(nèi)部定義的.所以它們的內(nèi)存布局如下:

img
解讀:變量`point , person`的內(nèi)存空間都在棧空間.不同的是,`Struct`的數(shù)據(jù)也存儲在變量的內(nèi)存中.而`Class`類型的變量內(nèi)存中存儲的是一個地址,一個指向堆空間類對象的內(nèi)存地址.`Class`類型的數(shù)據(jù)都存儲在堆空間中.

let point = Point(x: 10, y: 20)打斷點,可以看到Point的初始化代碼的匯編簡潔如下,根本就沒有調(diào)用malloc代碼:

image-20210402142418164

我們可以打個斷點,看看point 變量和 person 變量的內(nèi)存是不是如上圖分析的那樣,要搞清楚變量有沒有分配堆空間內(nèi)存,一個很重要的指標(biāo)就是是否調(diào)用了malloc方法,因為這個方法是向堆空間申請內(nèi)存的.我們在let person = Person()打上斷點,然后一步步執(zhí)行,會發(fā)現(xiàn)最終會調(diào)用malloc方法:

image-20210402143725775

在Swift中矗烛,創(chuàng)建類的實例對象辅柴,要向堆空間申請內(nèi)存,大概流程如下

Class.__allocating_init()

libswiftCore.dylib****:****swift_allocObject

libswiftCore.dylib****:****swift_slowAlloc

libsystem_malloc.dylib****:****malloc

**Class.__allocating_init()**

**libswiftCore.dylib****:****_swift_allocObject_**

**libswiftCore.dylib****:****swift_slowAlloc**

**libsystem_malloc.dylib****:****malloc**

直接打印出point變量和person變量的內(nèi)容,更直觀一些:

var point = Point(x: 10, y: 20)
var person = Person()
    
    print(Mems.ptr(ofVal:&person))
    print(Mems.memStr(ofVal:&person))
    print("------")
    print(Mems.ptr(ofVal:&point))
    print(Mems.memStr(ofVal:&point))
image-20210402144502149

如果想查看一個類型所創(chuàng)建出來的變量占用多少內(nèi)存,可以使用MemoryLayout<類型>.stride查看,比如:
查看String類型對象占用多少內(nèi)存:MemoryLayout<String>.stride
查看Int類型對象占用多少內(nèi)存:MemoryLayout<Int>.stride

如果想查看創(chuàng)建出來的指針變量在堆空間占用多少內(nèi)存,可以使用malloc_size(ptr: UnsafeRawPointer)

    var ptr = malloc(16)
    print(malloc_size(ptr))//16
    var ptr2 = malloc(17)
    print(malloc_size(ptr2))//32
image-20210402145530486

mac iOS中的malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù)

賦值操作不同,值類型是深拷貝,引用類型是淺拷貝

值類型的深度拷貝
image-20210402150031023

看看struct類型賦值操作的匯編語言:

func testValueType() {
    struct Point {
        var x: Int
        var y: Int
    }
    let p1 = Point(x: 10, y: 20)
    var p2 = p1
    p2.x = 11
    p2.y = 21
    print("123")
}
testValueType()
  0x100002653 <+19>:  movl   $0xa, %edi
    0x100002658 <+24>:  movl   $0x14, %esi
    0x10000265d <+29>:  callq  0x100002730               ; init(x: Swift.Int, y: Swift.Int) -> Point #1 in TestSwift.testValueType() -> () in Point #1 in TestSwift.testValueType() -> () at main.swift:11
    0x100002662 <+34>:  movq   %rax, -0x10(%rbp)
    0x100002666 <+38>:  movq   %rdx, -0x8(%rbp)
->  0x10000266a <+42>:  movq   %rax, -0x20(%rbp)
    0x10000266e <+46>:  movq   %rdx, -0x18(%rbp)
    0x100002672 <+50>:  movq   $0xb, -0x20(%rbp)
    0x10000267a <+58>:  movq   $0x15, -0x18(%rbp)
    0x100002682 <+66>:  movq   0x5a97(%rip), %rax        ; (void *)0x00007fff862faa70: type metadata for Any
    0x100002689 <+73>:  addq   $0x8, %rax
1

第一步:進(jìn)入Point 的 init() 函數(shù)

2

第二步:Point 的 init() 函數(shù) 函數(shù)內(nèi)部

img

第三步:深拷貝

在Swift標(biāo)準(zhǔn)庫中瞭吃,為了提升性能碌嘀,String、Array歪架、Dictionary股冗、Set采取了Copy On Write的技術(shù)

比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作

對于標(biāo)準(zhǔn)庫值類型的賦值操作和蚪,Swift 能確保最佳性能止状,所有沒必要為了保證最佳性能來避免賦值

引用類型的淺拷貝

引用賦值給var烹棉、let或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份

類似于制作一個文件的替身(快捷方式怯疤、鏈接)峦耘,指向的是同一個文件。屬于淺拷貝(shallow copy)

image-20210402152432157
func testReferenceType() {
    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    
    let s1 = Size(width: 10, height: 20)
    let s2 = s1
    s2.width = 11
    s2.height = 22
    print()
}
testReferenceType()
image-20210402153859366
0x1000022e1 <+1>:   movq   %rsp, %rbp
    0x1000022e4 <+4>:   pushq  %r13
    0x1000022e6 <+6>:   subq   $0xa8, %rsp
    0x1000022ed <+13>:  movq   $0x0, -0x10(%rbp)
    0x1000022f5 <+21>:  movq   $0x0, -0x18(%rbp)
    0x1000022fd <+29>:  xorl   %eax, %eax
    0x1000022ff <+31>:  movl   %eax, %ecx
    0x100002301 <+33>:  movq   %rcx, %rdi
    0x100002304 <+36>:  movq   %rcx, -0x50(%rbp)
    0x100002308 <+40>:  callq  0x100002470               ; type metadata accessor for Size #1 in TestSwift.testReferenceType() -> () at <compiler-generated>
    0x10000230d <+45>:  movl   $0xa, %edi
    0x100002312 <+50>:  movl   $0x14, %esi
    0x100002317 <+55>:  movq   %rax, %r13
    0x10000231a <+58>:  movq   %rdx, -0x58(%rbp)
    0x10000231e <+62>:  callq  0x100002490               ; __allocating_init(width: Swift.Int, height: Swift.Int) -> Size #1 in TestSwift.testReferenceType() -> () in Size #1 in TestSwift.testReferenceType() -> () at main.swift:14
    0x100002323 <+67>:  movq   %rax, -0x10(%rbp)
->  0x100002327 <+71>:  movq   %rax, %rdi
    0x10000232a <+74>:  movq   %rax, -0x60(%rbp)
    0x10000232e <+78>:  callq  0x1000072a6               ; symbol stub for: swift_retain
    0x100002333 <+83>:  movq   -0x60(%rbp), %rcx
    0x100002337 <+87>:  movq   %rcx, -0x18(%rbp)
    0x10000233b <+91>:  addq   $0x10, %rcx
    0x10000233f <+95>:  leaq   -0x30(%rbp), %rdx
    0x100002343 <+99>:  movl   $0x21, %esi
    0x100002348 <+104>: movq   %rcx, %rdi
    0x10000234b <+107>: movq   %rsi, -0x68(%rbp)
    0x10000234f <+111>: movq   %rdx, %rsi
    0x100002352 <+114>: movq   -0x68(%rbp), %rcx
    0x100002356 <+118>: movq   %rdx, -0x70(%rbp)
    0x10000235a <+122>: movq   %rcx, %rdx
    0x10000235d <+125>: movq   -0x50(%rbp), %rcx
    0x100002361 <+129>: movq   %rax, -0x78(%rbp)
image-20210402154648926
callq  0x100002490               ; __allocating_init(width: Swift.Int, height: Swift.Int) -> Size #1 in TestSwift.testReferenceType() -> () in Size #1 in TestSwift.testReferenceType() -> () at main.swift:14
movq   %rax, -0x10(%rbp)

函數(shù)callq后面執(zhí)行的匯編指令為movq %rax, -0x10(%rbp) rax旅薄、rdx常作為函數(shù)返回值使用,所以-0x10(%rbp)將存放s1的地址值泣崩。

我們現(xiàn)在讀取一下rax寄存器的值少梁。register read rax 通過rax存儲的內(nèi)存值,窺探一下內(nèi)存

image-20210402155126566
image-20210402155648867
image-20210402160853050

驗證是操作s1的內(nèi)存$0xb, 0x10(%rax)

image-20210402161002453

可以看到讀出來的rax儲存的是s1的地址,同樣是s2的地址

movq $0xb, 0x10(%rax)

image-20210402161450695
image-20210402161923901
image-20210402162107268

通過上面匯編代碼和內(nèi)存地址值剖析矫付,s1,s2同時指向堆中同一塊內(nèi)存區(qū)域凯沪。在s2中修改width的值,s1的類成員變量同樣被修改了买优。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妨马,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杀赢,更是在濱河造成了極大的恐慌烘跺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脂崔,死亡現(xiàn)場離奇詭異滤淳,居然都是意外死亡,警方通過查閱死者的電腦和手機砌左,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門脖咐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汇歹,你說我怎么就攤上這事屁擅。” “怎么了产弹?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵派歌,是天一觀的道長。 經(jīng)常有香客問我取视,道長硝皂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任作谭,我火速辦了婚禮稽物,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘折欠。我一直安慰自己贝或,他們只是感情好吼过,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咪奖,像睡著了一般盗忱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羊赵,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天趟佃,我揣著相機與錄音,去河邊找鬼昧捷。 笑死闲昭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的靡挥。 我是一名探鬼主播序矩,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跋破!你這毒婦竟也來了簸淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤毒返,失蹤者是張志新(化名)和其女友劉穎租幕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拧簸,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡令蛉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狡恬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珠叔。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弟劲,靈堂內(nèi)的尸體忽然破棺而出祷安,到底是詐尸還是另有隱情,我是刑警寧澤兔乞,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布汇鞭,位于F島的核電站,受9級特大地震影響庸追,放射性物質(zhì)發(fā)生泄漏霍骄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一淡溯、第九天 我趴在偏房一處隱蔽的房頂上張望读整。 院中可真熱鬧,春花似錦咱娶、人聲如沸米间。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屈糊。三九已至的榛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逻锐,已是汗流浹背夫晌。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昧诱,地道東北人慷丽。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鳄哭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纲熏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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