这里从特性、本质、区别来聊聊结构体和类,本文基于swift5
结构体
在Swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分,比如Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
//Int
@frozen public struct Int : FixedWidthInteger, SignedInteger {
...
}
//String
@frozen public struct String {
...
}
//Array
@frozen public struct Array<Element> {
...
}
//Dictionary
@frozen public struct Dictionary<Key, Value> where Key : Hashable {
...
}
结构体的内存结构
通过下面代码打印结构体占用的字节
func testAgeStruct() {
struct Age {
var cat: Int
var pig: Int
}
//占用内存大小
print(MemoryLayout<Age>.size)
//分配内存大小
print(MemoryLayout<Age>.stride)
//内存对齐
print(MemoryLayout<Age>.alignment)
let age = Age(cat: 1, pig: 2)
print(age)
}
另外,可以通过选中age,右键->View Memory of "age"查看内存

内存地址为0x7ffeefbff4a0,内存中的内容为所在地址的16字节,如下图

结构体的初始化器
编译器会根据情况,为结构体生成多个初始化器,保证所有成员变量都有初始值,从而保证安全。
下面通过Age结构体来看看在各种情况下,编译器生成的初始化器。
- 成员变量都没有初始值
struct Age {
var cat: Int
var pig: Int
}
var A1 = Age(cat: 5, pig: 6)
var A2 = Age(cat: 5) //Missing argument for parameter 'pig' in call
var A3 = Age(pig: 6) //Missing argument for parameter 'cat' in call
var A4 = Age() //Missing arguments for parameters 'cat', 'pig' in call
因都cat、pig都没有初始值,编译器生成的是A1这种初始化器,需传入cat、pig,A2、A3、A4都会报错
- 成员变量部分有初始值
struct Age {
var cat: Int = 0
var pig: Int
}
var A1 = Age(cat: 5, pig: 6)
var A2 = Age(cat: 5) //Missing argument for parameter 'pig' in call
var A3 = Age(pig: 6)
var A4 = Age() //Missing argument for parameter 'pig' in call
因cat有初始值,编译器生成的是A1、A3这种初始化器,所以A2、A4都会报错
- 成员变量都有初始值
struct Age {
var cat: Int = 0
var pig: Int = 0
}
var A1 = Age(cat: 5, pig: 6)
var A2 = Age(cat: 5)
var A3 = Age(pig: 6)
var A4 = Age()
A1、A2、A3、A4都可以编译通过
注意:如果我们在定义结构体时,自定义了初始化器,那么编译器就不会再自动生成其他初始化器。
struct Age {
var cat: Int = 0
var pig: Int = 0
init(cat: Int, pig: Int) {
self.cat = cat
self.pig = pig
}
}
var A1 = Age(cat: 5, pig: 6)
var A2 = Age(cat: 5)
var A3 = Age(pig: 6)
var A4 = Age()
A2、A3、A4都会报错
初始化器的本质
下面通过汇编代码来看手写初始化器和编译器自动生成初始化器
- 编译器自动生成初始化器
代码



- 手动写初始化方法
代码

testAgeStruct2()方法的汇编代码


通过以上两种情况汇编代码,我们可以看到,编译器生成的初始化器和我们手写效果是一样的。
类
初始化器
看看下面代码,会有什么结果?
func testAgeClass() {
class Age {
var cat: Int
var pig: Int
}
let a1 = Age()
}
编译器会报以下错:
- Class 'Age' has no initializers
- 'Age' cannot be constructed because it has no accessible initializers
也就是说,与结构体不同,类中如果没有给初始化值,编译器是不会生成初始化器
再看看下面这段代码
func testAgeClass() {
class Age {
var cat: Int = 0
var pig: Int = 0
}
let A1 = Age()
let A2 = Age(cat: 1, pig: 2) // 报错
let A3 = Age(cat: 1) //报错
let A4 = Age(pig: 2) //报错
}
上面代码,只有A1是编译器不报错,A2、A3、A4都会报错。如果是结构体,那么编译器就会生成上面四种初始化器,都能编译通过。 可以看出,与结构体不同,类中如果给初始化值,编译器只会生成无参的初始化器。
下面我们再通过汇编看看下面代码,初始化器是怎么实现的
func testAgeClass() {
class Age {
var cat: Int = 1
var pig: Int = 2
}
let a1 = Age() //在此处打断点
}
testAgeClass()
初始化器的实现



结构体和类的本质区别
结构体是值类型,而类是引用类型
值类型在传递和赋值时进行复制,直接把所有内容拷贝一份,是深拷贝。
引用类型在赋值或给参数传参,是将内存地址拷贝一份,是浅拷贝。
下面从汇编角度分别看看结构体和类是在栈中还是堆空间申请内存的 类
func testAgeClass() {
class Age {
var cat: Int = 1
var pig: Int = 2
}
let a1 = Age()
}
testAgeClass()
汇编





Swift中创建类的实例对象,向堆空间申请内存流程大致如上。
结构体
func testAgeClass() {
struct Age {
var cat: Int = 1
var pig: Int = 2
}
let a1 = Age()
}
testAgeClass()
汇编代码


在内存中,值类型是在栈上进行存储和操作的。引用类型是在堆上进行存储和操作的。相比栈上的操作,从上面汇编代码,也可以看出,堆上的操作更加复杂和耗时。因此,苹果官方也是推荐使用结构体,以提高App的运行效率。
struct相比class,使用上有哪些优势?
更加安全
由于struct是结构较小,相比一个class的实例被引用多次,struct更加安全
无须担心内存泄漏问题
struct是值类型,是在栈空间上,由系统自动管理内存,避免了内存泄漏等问题
自动生成带参数初始化器
struct中自动生成带参数初始化器,比class中要手动去生成方便不少
Copy-on-Write
Copy-on-Write是一种用来优化占用内存大的值类型的拷贝操作的机制。
- 对于Int、Bool等基本类型的值类型,它们在赋值的时候就会发生拷贝,它们没有Copy-on-Write这一特性
- 对于String、Array、Dictionary、Set类型,当它们赋值的时候不会发生拷贝,只有在修改的之后才会发生拷贝,即Copy-on-Write。
Copy-on-Write技术使得struct中的内存使用效率更高。
建议:不需要修改的变量,建议定义为let。这样后面不小心修改了它,编译时会报错提醒。
最后:因个人水平有限,如有不对的地方,还望大神提点。如若对你有一些帮助,能给个赞,那就更感激不尽 😄