枚举(enum)
枚举的成员类型
相对于OC的枚举来说.Swift中的枚举功能要更加强大
OC中枚举值只能是int型.而Swift中的枚举值可以是int,char,String.同一个枚举中的值也可以是不同类型.
枚举的原始值(rawValue)
当我们给枚举设置了类型后,枚举的成员都必须是相同类型.我们也可以用一个默认值将每个枚举值关联起来.
当设置为String和Int类型时,swift会自动为枚举成员设置原始值
enum Test : String{
case test1 = "test1"
case test2 = "test2"
}
enum Test1 : String{
case test1
case test2
}
print(Test.test1.rawValue) //test1
print(Test1.test1.rawValue) //test1
enum TestNumber:Int {
case first
case seconde
}
enum TestNumber1:Int {
case first = 0
case seconde = 1
}
print(TestNumber.first.rawValue) //0
print(TestNumber1.first.rawValue) //0
枚举的关联值
有时候会将枚举的成员值跟其他类型的值关联存储在一起.
enum Time {
case intValue(hour:Int,min:Int,sec:Int)
case stringValue(String)
}
var time = Time.intValue(hour: 10, min: 10, sec: 10)
time = .stringValue("10:20:21")
枚举的内存分配
没有关联值的枚举.有多个case的情况下占用的内存为1个字节.只用来存放枚举的成员值.而枚举的原始值,通过其他方法获取.如果只有一个case.不占用内存
enum Test {
case number
case other
}
MemoryLayout<Test>.size //1
MemoryLayout<Test>.stride //1
MemoryLayout<Test>.alignment //1
var t = Test.number
//内存分布
//00
t = Test.other
//01
enum Test1 {
case number
}
MemoryLayout<Test1>.size //0
MemoryLayout<Test1>.stride //1
MemoryLayout<Test1>.alignment //1
存在关联值的枚举.在内存中占用的字节为占用字节最大关联值的字节大小+1.
最后的1个字节存放枚举的成员值.也可以理解为序号.表明当前是哪个case.而前面的字节存放关联值
enum Test {
case number(Int, Int, Int, Int)
case other
}
MemoryLayout<Test>.size //33
MemoryLayout<Test>.stride //40
MemoryLayout<Test>.alignment //8
var t = Test.number(1, 2, 3, 4)
//内存分布
//01 00 00 00 00 00 00 00
//02 00 00 00 00 00 00 00
//03 00 00 00 00 00 00 00
//04 00 00 00 00 00 00 00
//00
//00 00 00 00 00 00 00
t = Test.other
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//01
//00 00 00 00 00 00 00
结构体(struct)
swift中 绝大多数公开类型都是结构体. 枚举和类只占小部分
Bool Int Double String Array Dictionary等常见类型都是结构体
结构体的初始化器
结构体会自动生成初始化器
struct A{
var a1: Int
var a2: Int
}
var a1 = A(a1: 10, a2: 10)
var a2 = A(a2: 10) //Missing argument for parameter 'a1' in call
var a3 = A(a1: 10) //Missing argument for parameter 'a2' in call
var a4 = A() //Missing arguments for parameters 'a1', 'a2' in call
struct C {
var a:Int = 0
var b:Int = 0
var c:Int = 0
func printValue() {
print(a,b,c)
}
}
var c1 = C(a: 10, b: 10, c: 10)
var c2 = C(a: 10)
var c3 = C(b: 10)
var c4 = C(c: 10)
var c5 = C()
c1.printValue() //10 10 10
c2.printValue() //10 0 0
c3.printValue() //0 10 0
c4.printValue() //0 0 10
c5.printValue() //0 0 0
struct C1 {
var a:Int
var b:Int
var c:Int
init() {
self.a = 0;
self.b = 0;
self.c = 0;
}
func printValue() {
print(a,b,c)
}
}
var c1 = C1(a: 10, b: 10, c: 10) //Argument passed to call that takes no arguments
var c2 = C1(a: 10) //Argument passed to call that takes no arguments
var c3 = C1(b: 10) //Argument passed to call that takes no arguments
var c4 = C1(c: 10) //Argument passed to call that takes no arguments
var c5 = C1()
c1.printValue() //10 10 10
c2.printValue() //10 0 0
c3.printValue() //0 10 0
c4.printValue() //0 0 10
c5.printValue() //0 0 0
对比A跟C可以看出.当当我们没有声明初始化器的时候.会根据情况自动生成初始化器.
- 如果结构体声明成员变量时给了初始值,则会生成多个初始化器.
- 如果成员变量没有初始值,则只会自动生成一个需要给所有成员赋值的初始化器
结论:结构体初始化完成时,必须保证所有的成员变量有值
对比C跟C1两个基本相同的结构体.可以看到.如果定义了初始化器,则其他的初始化器都不会自动生成.
结构体的内存布局
struct C {
var a:Int = 0
var b:Int = 0
var c:Int = 0
func printValue() {
print(a,b,c)
}
}
var c1 = C(a: 10, b: 10, c: 10)
print(MemoryLayout.size(ofValue: c1))//24
查看汇编代码发现,结构体实例化时调用了struct.C.init方法来进行结构体的实例化.
-> 0x100003848 <+8>: movl $0xa, %eax
0x10000384d <+13>: movl %edi, -0x1c(%rbp)
0x100003850 <+16>: movq %rax, %rdi
0x100003853 <+19>: movq %rsi, -0x28(%rbp)
0x100003857 <+23>: movq %rax, %rsi
0x10000385a <+26>: movq %rax, %rdx
0x10000385d <+29>: callq 0x100003b90 ; struct.C.init(a: Swift.Int, b: Swift.Int, c: Swift.Int) -> struct.C at main.swift:10
结构体C中.定义了3个Int成员变量.各占8个字节.通过MemoryLayout.size(ofValue: c1)得到结果24.所以可以得出结论.结构体的内存大小为所有成员变量的总和.那在内存上它是怎么分布的呢.
这里我们通过断点,窥探内存分布
0x100008038 | 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00
发现0x100008038后的连续24个字节,每八个字节都是十六进制值0000000A,也就是十进制10.说明结构体的内存是在栈上连续分布的.每n位存放对应的成员变量值.n=成员变量类型占用字节大小.
结构体赋值
当我们把一个现有的结构体变量赋值给另一个结构体变量,并且修改其中一个结构体的某个成员值,会发生什么呢
struct C {
var a:Int = 0
var b:Int = 0
var c:Int = 0
func printValue() {
print(a,b,c)
}
}
var c1 = C(a: 10, b: 10, c: 10)
var c2 = c1
c1.a = 20
c1.printValue() //20 10 10
c2.printValue() //10 10 10
结论说明结构体之间的赋值.是深拷贝.
当一个结构体被赋值给另一个结构体时,会重新开辟一段连续的内存,再将对应的值放入对应的位置.当完成这一操作后,两个结构体完全独立.
我们可以按照上面的方式看看他们的内存分布
c1--0x100008038 | 14 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00
c2--0x100008050 | 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00
可以发现两者的内存分布是正好相差了24个字节.也印证了上面说的,结构体在栈上连续分布.
结构体的写时复制(copy on write)
在swift中,String, Array, Dictionary等结构体类型,为了不浪费内存,采取了copy on write操作.
即:当上述类型赋值时,如果没有进行写的操作.两个变量都会指向同一块地址.当进行了写操作时,才会执行拷贝操作.
类(class)
类的初始化器
类与结构体长得很相似.但是类生成初始化器的规则并不同.
class A {
var a = 0
var b = 0
}
let a = A()
class B { //Class 'B' has no initializers
var a : Int
var b : Int
}
let b = B() //'B' cannot be constructed because it has no accessible initializers
class C {
var a : Int
var b : Int
init() {} //Return from initializer without initializing all stored properties
}
let c = C()
class D {
var a : Int
var b : Int
init() {
self.a = 0
self.b = 0
}
}
let d = D()
class E {
var a : Int
var b : Int
init(a:Int, b:Int) {
self.a = a
self.b = b
}
}
let e = E(a: 10, b: 10)
通过上面的例子可以发现.如果在声明类的时候,有以下几种可能
- 类的成员变量添加了初始值.则会自动生成无参的初始化器.
- 类成员变量没有初始值,声明无参初始化器,需要在初始化器中为成员变量赋值
- 手动声明带参数的初始化器
类的内存分布
下面通过汇编,可以看到在初始化类时,调用了__allocating_init.
class A {
var a = 10
var b = 10
}
let a = A()
-> 0x100003251 <+17>: movq %rcx, %rdi
0x100003254 <+20>: movq %rsi, -0x18(%rbp)
0x100003258 <+24>: callq 0x1000032a0 ; type metadata accessor for struct.A at <compiler-generated>
0x10000325d <+29>: movq %rax, %r13
0x100003260 <+32>: movq %rdx, -0x20(%rbp)
0x100003264 <+36>: callq 0x1000036e0 ; struct.A.__allocating_init() -> struct.A at main.swift:11
继续往下跟踪,_allocating_init->_swift_allocObject -> swift_slow_alloc -> malloc
会发现最终调用了malloc方法.说明在初始化类实例对象的过程中.会在堆空间开辟一段新的内存,用来存放实例对象的内容.我们来看看堆空间存放了什么
a | 0x10057fc20 28 82 00 00 01 00 00 00
03 00 00 00 02 00 00 00
0A 00 00 00 00 00 00 00
0A 00 00 00 00 00 00 00
通过查看内存分布.发现有两个0A的值.正好就是a和b的值.而前面的16位.分别存放了对象的引用计数以及类型信息
类的赋值
class A {
var a = 10
var b = 10
func printValue() {
print(a,b)
}
}
let a = A()
let b = a
b.a = 20
a.printValue() //20 10
b.printValue() //20 10
与结构体不同,类赋值时,是浅拷贝.只是将堆地址复制了一份.修改其中一个实例对象变量的值,都会引起另一个值的变化
类和结构体的不同
类型上:
- 结构体:值类型 赋值给另一个参数.等于值传递.会复制多一份内存.且两部分内存完全独立.深拷贝
- 类:引用类型 赋值时时引用传递,只传递了指针 浅拷贝
在内存中:
- 结构体:栈上连续的N个字节.N = 结构体的size,结构体可能存在在栈或全局区(数据段)
- 类:栈上存放指针变量.指针指向堆中实际类对象的地址,类一直存在堆区.但是指向它的指针有可能存在栈或全局区(数据段)
初始化:
结构体:只调用init方法
类:
_allocating_init
->_swift_allocObject
->swift_slow_alloc
->malloc