文章首发于我的个人博客
前言
类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
eg 如下代码不会报错
1 | struct Point { |
但是如果改成了类就不能编译通过
1 | class Point { |
类的初始化器
- 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
- 成员的初始化是在这个初始化器中完成的
以下2段代码完全等效
1 | struct Point { |
结构体与类的本质区别
结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
eg:我们有如下的结构体point 和类 size
1 | // 类size |
我们假设 执行完test() 之后,point的内存地址为 0x10000 size的内存地址为 0x10010
可以用一幅图来表示
上图表示,point是值拷贝,直接把 3 和 4 放在了point对应的内存中,而 指针变量size是引用拷贝,是放了 Size() 的指针 0x90000 ,而对应的 堆空间 0x90000中才真正的存放1和2,当然了,前面有16个字节,存放了类的信息,和引用计数,因为Swift和OC一样使用的引用计数来内存管理的,所以Size对象用了32个字节
汇编验证
代码如下
1 | unc test1(){ |
汇编验证验证结构体
上面的代码,先在 var point = Point() 处打断点
1 | testSwift`__allocating_init() in Size #1 in test1(): |
从
1 | 0x10000103e <+14>: callq 0x100001250 ; type metadata accessor for Size #1 in testSwift.test1() -> () at <compiler-generated> |
处执行lldb命令 si 跟踪进去
1 |
|
可以看到赋值操作 直接是把 $0x3 和 $0x4 赋值给栈空间 (-0x10(%rbp) 和 -0x8(%rbp) )的,没有调用malloc alloc 等方法,也就是没有开辟堆空间
汇编验证验证类
上面的代码,先在 var size = Size() 处打断点
1 | 0x100000fe0 <+0>: pushq %rbp |
进入
1 | 0x100001009 <+41>: callq 0x100001030 ; __allocating_init() -> Size #1 in testSwift.test1() -> () in Size #1 in testSwift.test1() -> () at main.swift:15 |
一路跟踪进入,最终来到了如下图所示位置
也就是确实分配了堆空间,验证了我们前面的结论
对象的堆空间申请过程
- 在Swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下
1 | Class.__allocating_init() |
- 在Mac、iOS中的malloc函数分配的内存大小总是16的倍数
- 通过class_getInstanceSize可以得知:类的对象至少需要占用多少内存
eg:
1 | class Point { |
值类型
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术
- 比如仅当有“写”操作时,才会真正执行拷贝操作
- 对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值
- 比如仅当有“写”操作时,才会真正执行拷贝操作
建议:不需要修改的,尽量定义成let
引用类型
- 引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
- 类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy)
枚举、结构体、类都可以定义方法
一般把定义在枚举、结构体、类内部的函数,叫做方法
1 | // 类中定义方法 |
参考资料: