結(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)
,有可能會生成多個初始化器.生成初始化器的原則是:保證所有成員都有值
.
比如:
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)體自定義初始化器,但是一旦自定義初始化器后,編譯器就不會再自動生成初始化器:
自定義初始化器后,只有自定義的初始化器有效,系統(tǒng)不會再生成初始化器
其實我們自定義的初始化器和系統(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)體很相似,但是==如果類的成員沒有初始值,編譯器不會為類生成初始化器==:
- 如果類的成員都有初始值,編譯器才會為類生成無參初始化器:
- 成員的初始化是在這個初始化器中完成的
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)體是值類型,類是引用類型
//類
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)存布局如下:
解讀:變量`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
代碼:
我們可以打個斷點,看看point
變量和 person
變量的內(nèi)存是不是如上圖分析的那樣,要搞清楚變量有沒有分配堆空間內(nèi)存,一個很重要的指標(biāo)就是是否調(diào)用了malloc
方法,因為這個方法是向堆空間申請內(nèi)存的.我們在let person = Person()
打上斷點,然后一步步執(zhí)行,會發(fā)現(xiàn)最終會調(diào)用malloc
方法:
在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))
如果想查看一個類型所創(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
mac iOS中的malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù)
賦值操作不同,值類型是深拷貝,引用類型是淺拷貝
值類型的深度拷貝
看看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
第一步:進(jìn)入Point 的 init() 函數(shù)
第二步:Point 的 init() 函數(shù) 函數(shù)內(nèi)部
第三步:深拷貝
在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)
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()
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)
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)存
驗證是操作s1的內(nèi)存$0xb, 0x10(%rax)
可以看到讀出來的rax儲存的是s1的地址,同樣是s2的地址
movq $0xb, 0x10(%rax)
通過上面匯編代碼和內(nèi)存地址值剖析矫付,s1,s2同時指向堆中同一塊內(nèi)存區(qū)域凯沪。在s2中修改width的值,s1的類成員變量同樣被修改了买优。