结构体
- 在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)
- 结构内部的所有成员,专业术语叫做
存储属性
结构体的初始化器
- 编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值;
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
}
}
结构体的内存结构
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.size) //17
print(MemoryLayout.stride) //24
print(MemoryLayout.alignment) //8
类
- 类的定义与结构体的类似,但编译器并没有为类自动生成可以传入成员值的初始化器;
class PointXY{
var x: Int = 0
var y: Int = 0
}
import Foundation
//调用
var pointXY = PointXY()
- 因为成员x与y有默认值,所以编译器自动生成了无参的初始化器;
- 若没有默认值,那么编译器不会生成任何初始化器;
结构体与类的本质区别
- 结构体是值类型,枚举也是值类型;
- 类是引用类型,指针类型;
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()
- 结构体变量point是数值类型,在栈区分配内存空间,其栈地址为0x1000083B0,然后后面的16个字节存储的是其成员x,y的值;
- Size类的实例对象size是指针类型,size是指针变量,,在栈区分配内存空间,占8个字节,其栈地址为0x1000083C0,其内存地址中存储的是实例对象的内存地址即0x100657540,
x/4gx 0x100657540
可以获取实例对象的内容,打印出4个8字节的内容,可以看到后面的16个字节存储的是其成员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寄存器;
- 执行完初始化器方法,紧接着执行以下指令:
-
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
值类型,引用类型的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
}
}
-
let point
是常量值类型,即point的值不可以修改;
-
let size
是常量指针,即size的值不可以修改,也就是size的指向不可以修改,但其指向的对象可以修改;
对象申请堆内存空间的过程
- 在Swift中,创建实例对象,需要向堆申请内存空间,其详细过程如下所示:
- Class.__allocating_init()
- libSwiftCore.dylib:swift_allocObject
- libSwiftCore.dylib:swift_slowAlloc
- libsystem_malloc.dylib:malloc
- 在Mac,iOS中maclloc分配堆内存空间函数,采用16字节内存对齐的算法;
-
class_getInstanceSize()
函数是获取实例对象占用的内存大小,采用的是8字节内存对齐算法;
import Foundation
func TestInstanceSize() -> Void {
class Point {
var x: Int = 100 //8
var y: Int = 200 //8
var origin: Bool = false //1
//再加上前面的16个字节 总共实际会占用33个字节
}
let point = Point()
//内存对齐之后的内存大小 8字节对齐
print(class_getInstanceSize(type(of: point))) //40
print(class_getInstanceSize(Point.self)) //40
//maclloc申请堆空间内存 会采用16字节对齐,
//所以最终会分配48个字节
}
TestInstanceSize()
- point实例对象实际占用的内存大小为33个字节;
嵌套类型
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()
- 方法是存放在代码段,是不会占用实例对象的内存空间;