結構體
- 在Swift標準庫中,絕大多數公開的類型都是
結構體
,而枚舉和類只占很少的一部分; - Bool,Int,Double,String,Array,Dictionary等常見類型都是結構體;
struct Date {
var year: Int
var month: Int
var day: Int
}
import Foundation
var date = Date(year: 2021, month: 7, day: 29)
- 定義了一個結構體Date,其內部有三個成員;
- 所有的結構體都有一個編譯器自動生成的初始化器(初始化方法,構造方法),例如
Date(year: 2021, month: 7, day: 29)
- 結構內部的所有成員,專業(yè)術語叫做
存儲屬性
結構體的初始化器
- 編譯器會根據情況,可能會為結構體生成多個初始化器,宗旨是:保證所有成員都有初始值;
struct Point {
var x: Int = 0
var y: Int = 0
}
import Foundation
var point = Point(x: 10, y: 10)
point = Point(x: 20)
point = Point(y: 30)
point = Point()
- 由于成員x,y有默認值,所有編譯器會自動生成4個初始化器;
自定義初始化器
- 在定義結構體時,一旦自定義了初始化器,編譯器就不會自動生成其他的初始化器;
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int,y: Int) {
self.x = x
self.y = y
}
}
-
init
為自定義初始化器;
結構體的內存結構
struct Point {
var x: Int = 0
var y: Int = 0
var origin: Bool = false
init(x: Int,y: Int,origin: Bool) {
self.x = x
self.y = y
self.origin = origin
}
}
import Foundation
var point = Point(x: 10, y: 20,origin: true)
print(MemoryLayout<Point>.size) //17
print(MemoryLayout<Point>.stride) //24
print(MemoryLayout<Point>.alignment) //8
- 結構體變量point的內存布局如下所示:
Snip20210730_58.png
類
- 類的定義與結構體的類似,但編譯器并沒有為類自動生成可以傳入成員值的初始化器;
class PointXY{
var x: Int = 0
var y: Int = 0
}
import Foundation
//調用
var pointXY = PointXY()
- 因為成員x與y有默認值,所以編譯器自動生成了無參的初始化器;
- 若沒有默認值,那么編譯器不會生成任何初始化器;
結構體與類的本質區(qū)別
- 結構體是值類型,枚舉也是值類型;
- 類是引用類型,指針類型;
struct Point {
var x: Int = 3
var y: Int = 4
}
class Size {
var width: Int = 5
var height: Int = 6
}
import Foundation
var point = Point()
var size = Size()
Snip20210731_59.png
- 結構體變量point是數值類型,在棧區(qū)分配內存空間,其棧地址為0x1000083B0,然后后面的16個字節(jié)存儲的是其成員x,y的值;
- Size類的實例對象size是指針類型,size是指針變量,,在棧區(qū)分配內存空間,占8個字節(jié),其棧地址為0x1000083C0,其內存地址中存儲的是實例對象的內存地址即0x100657540,
x/4gx 0x100657540
可以獲取實例對象的內容,打印出4個8字節(jié)的內容,可以看到后面的16個字節(jié)存儲的是其成員width,height的值;
值類型
- 值類型賦值給let,var或者函數傳參,是直接將所有的內容拷貝一份,屬于深拷貝;
struct Point {
var x: Int
var y: Int
}
func testZLX() -> Void {
var point = Point(x: 10, y: 20)
var point1 = point
point1.x = 11
point1.y = 22
print(point.x)
print(point.y)
}
import Foundation
testZLX()
- 當斷點停在
var point = Point(x: 10, y: 20)
所在行,查看匯編代碼如下:
0x100003040 <+0>: pushq %rbp
0x100003041 <+1>: movq %rsp, %rbp
0x100003044 <+4>: subq $0x90, %rsp
0x10000304b <+11>: xorps %xmm0, %xmm0
0x10000304e <+14>: movaps %xmm0, -0x10(%rbp)
0x100003052 <+18>: movaps %xmm0, -0x20(%rbp)
0x100003056 <+22>: movl $0xa, %edi
0x10000305b <+27>: movl $0x14, %esi
-> 0x100003060 <+32>: callq 0x100003030 ; Swift09_枚舉的內存布局.Point.init(x: Swift.Int, y: Swift.Int) -> Swift09_枚舉的內存布局.Point at main.swift:32
0x100003065 <+37>: movq %rax, -0x10(%rbp)
0x100003069 <+41>: movq %rdx, -0x8(%rbp)
0x10000306d <+45>: movq %rax, -0x20(%rbp)
0x100003071 <+49>: movq %rdx, -0x18(%rbp)
0x100003075 <+53>: movq $0xb, -0x20(%rbp)
0x10000307d <+61>: movq $0x16, -0x18(%rbp)
0x100003085 <+69>: movq 0xf7c(%rip), %rcx ; (void *)0x00007fff80cc5020: type metadata for Any
-
movl $0xa, %edi
是將10存入edi寄存器; -
movl $0x14, %esi
是將20存入esi寄存器; - 然后進入初始化器,匯編如下:
Swift09_枚舉的內存布局`Point.init(x:y:):
-> 0x100003030 <+0>: pushq %rbp
0x100003031 <+1>: movq %rsp, %rbp
0x100003034 <+4>: movq %rdi, %rax
0x100003037 <+7>: movq %rsi, %rdx
0x10000303a <+10>: popq %rbp
0x10000303b <+11>: retq
-
movq %rdi, %rax
將寄存器rdi中的值存入rax寄存器,即將10存入rax寄存器; -
movq %rsi, %rdx
將寄存器rsi中的值存入rdx寄存器,即將20存入rdx寄存器; - 執(zhí)行完初始化器方法,緊接著執(zhí)行以下指令:
-
movq %rax, -0x10(%rbp)
將rax寄存器中的值10,寫入內存地址(rbp - 0x10)中; -
movq %rdx, -0x8(%rbp)
將rdx寄存器中的值20,寫入內存地址(rbp - 0x8)中; -
(rbp - 0x10)
本質就是變量point的內存地址; -
movq %rax, -0x20(%rbp)
將rax寄存器中的值10,寫入內存地址(rbp - 0x20)中; -
movq %rdx, -0x18(%rbp)
將rdx寄存器中的值20,寫入內存地址(rbp - 0x18)中; -
(rbp - 0x18)
本質就是變量point1的內存地址; - 從這里可以看出point與point1是兩份不同的內存地址,從而證明了值類型的傳遞屬于深拷貝;
- 那么更改point1的成員值,不會影響到point;
值類型的賦值操作
import Foundation
var str1 = "123"
var str2 = str1
str2.append("456")
print("\(str1) -- \(str2)") //123 --123456
var arr1 = [1,2,3]
var arr2 = arr1
arr2.append(4)
print(arr1) //[1,2,3]
print(arr2) //[1,2,3,4]
var dic1 = ["name":"li","age":"10"]
var dic2 = dic1
dic2["height"] = "175"
print(dic1) //["name": "li", "age": "10"]
print(dic2) //["name": "li", "age": "10", "height": "175"]
- String,Array,Dictionary都是值類型,則值傳遞時都是深拷貝,所以更改其中一個變量的內容,不會影響到副本中的內容;
- 在Swift標準庫中,為了提升性能,String,Array,Dictionary,Set采用了Copy On Write技術,即只有當在真正改寫內容時才會進行深拷貝,否則本體與副本使用同一塊內存;
引用類型
- 引用賦值給let,var或者給函數傳參,是將內存地址(引用)拷貝一份,屬于淺拷貝;
class Size {
var width: Int = 3
var height: Int = 4
}
import Foundation
var size1 = Size()
var size2 = size1
- 原理如下圖所示:
Snip20210731_60.png
值類型,引用類型的let
struct Point {
var x: Int
var y: Int
}
class Size {
var width: Int
var height: Int
init(width: Int,height: Int) {
self.width = width
self.height = height
}
}
Snip20210731_61.png
-
let point
是常量值類型,即point的值不可以修改; -
let size
是常量指針,即size的值不可以修改,也就是size的指向不可以修改,但其指向的對象可以修改;
對象申請堆內存空間的過程
- 在Swift中,創(chuàng)建實例對象,需要向堆申請內存空間,其詳細過程如下所示:
- Class.__allocating_init()
- libSwiftCore.dylib:swift_allocObject
- libSwiftCore.dylib:swift_slowAlloc
- libsystem_malloc.dylib:malloc
- 在Mac,iOS中maclloc分配堆內存空間函數,采用16字節(jié)內存對齊的算法;
-
class_getInstanceSize()
函數是獲取實例對象占用的內存大小,采用的是8字節(jié)內存對齊算法;
import Foundation
func TestInstanceSize() -> Void {
class Point {
var x: Int = 100 //8
var y: Int = 200 //8
var origin: Bool = false //1
//再加上前面的16個字節(jié) 總共實際會占用33個字節(jié)
}
let point = Point()
//內存對齊之后的內存大小 8字節(jié)對齊
print(class_getInstanceSize(type(of: point))) //40
print(class_getInstanceSize(Point.self)) //40
//maclloc申請堆空間內存 會采用16字節(jié)對齊,
//所以最終會分配48個字節(jié)
}
TestInstanceSize()
- point實例對象實際占用的內存大小為33個字節(jié);
嵌套類型
struct Test {
enum Rank : Int {
case two = 2,three,four
case J,Q,K
}
}
import Foundation
print(Test.Rank.two.rawValue) //2
- 結構體中嵌套枚舉類型;
枚舉,結構體,類都可以定義方法
- 一般把定義在枚舉,結構體,類內部的函數,叫做方法;
class Point {
var x: Int = 100 //8
var y: Int = 200 //8
var origin: Bool = false //1
func show() -> Void {
print("Point class -- show")
}
}
struct Size {
var width: Int
var height: Int
func show() -> Void {
print("Size struct -- show")
}
}
enum Grade : Int {
case perfect = 1,great,good,bad
func show() -> Void {
print("Grade enum -- show")
}
}
import Foundation
var point = Point()
point.show()
var size = Size(width: 100, height: 200)
size.show()
var grade = Grade.great
grade.show()
- 調試結果如下:
Snip20210731_62.png
- 方法是存放在代碼段,是不會占用實例對象的內存空間;