Swift-类与结构体(1)
一、类与结构体
在这里,我们主要探究在Swift中类与结构体之间的相同与不同,以及在我们平时使用的过程中应该如何选择使用。
(1)结构体的特性:
struct Student1{
var age:Int
var name:String
}
let s1 = Student1(age: 19, name: "swift")
struct Student2{
var age:Int = 0
var name:String = "swift"
}
let s2 = Student2()
- 结构体中提供的默认的构造函数 init(age:Int,name:string) 当我们构造一个结构体实体p时就会借助这个默认构造器来初始化,当然构造函数也可以自己定义,在自己指定的构造器中需要对未初始化的属性进行赋值,如果我们在定义属性时就给定了初始化值,可以直接通过let s2 = Student2()来进行初始化。
- 通过如下的代码看出,结构体是一种值类型,分配在栈区,从s = s1中可以看出,s和 s1的地址不同,说明结构体是在栈区重新分配的,但是如果结构体中有引用类型的成员变量,则在复制时不会重新分配。
struct Student1{ var age:Int var name:String } var s1 = Student1(age: 19, name: "swift") var s = s1

- 从地址首位0x00000可以看出s,s1均位于栈区。
- 结构体第一个成员的地址就是结构体的首地址(结构体在栈中是连续的)
(2) 类特性
类是一种引用类型,他分配在堆区(不连续,第一个成员的地址和类的首地址不一致)中,也就是说一个类类型的变量不是直接存储具体的实例对象,而是存储当前具体实例的内存地址的引用,复制后的实例和成员属性的值都是指向原有的地址空间。
class Student1{ var age:Int var name:String }//Class 'Student1' has no initializers
(类不会默认生成成员初始化器)我们需要知道在声明类的属性时 ,我们如果既没有初始化方法又没有给类的属性定义初始值时,编译器会报错。我们需要在定义类时给定init方法或者为类的存储属性定义初始值。初始化器在初始化前不可以调用任何实例方法、不能读取任何属性的值、不能引用self作为值
class Student1{ var age:Int var name:String //直接赋值 var age:Int?
var name:String? //指定初始化器 init(age:Int,name:String){ self.age = age self.name = name } //便捷初始化器 convenience init(){ self.init(age:19,name:"swift") } //可失败的初始化器 init?(age:Int,name:String){ if age < 18{ return nil } self.age = age self.name = name } //必要初始化器 required init(age:Int,name:String){ self.age = age self.name = name } }
- 第一种:可以通过对存储属性直接赋值来初始化
- 第二种:指定初始化器,通常指定初始化器只有一个 可携带0或多个参数
- 第三种:编辑初始化器,编辑初始化器可以有多个,在编辑初始化器中必须调用相同类型的初始化器
- 第四种:可失败的初始化器,主要针对初始化失败的情况,在可失败的初始化器中可写return nil语句
- 第五种:必要初始化器:这个关键字要求所有的子类必须实现该初始化器
那么在继承时,初始化器又是如何的呢?
class Student:Person{ var subAge:Int init(subAge:Int){ self.subAge = subAge super.init(age: 10) } }
- 子类的构造函数必须调用父类的构造函数
- 子类的指定初始化器中需要先初始化自己的属性,再初始化父类的指定初始化器
- 子类如果要修改继承属性,必须在调用父类的初始化器后进行,否则继承属性就会被父类的初始化器所覆盖
(3)类和结构体的不同
从最直观的角度来讲,类和结构体最显著的区别在于类是引用类型,而结构体是值类。
相同点
- 定义存储属性
- 定义方法
- 定义初始化器
- 遵循协议来提供某种功能
- 都可使用extension来扩展功能(2)中会讲到
- 可以定义下标通过下标语法来提供对其值的访问
不同点
- 类有继承,结构体没有
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
- 在类型转化时可以在运行时检查和解释类实例的类型
二、类的生命周期
- OC通过clang编译器,编译成IR,再生成可执行文件
- swift通过swift编译器编译成IR,再生成可执行文件
- swift code的编译过程中会生成SIL文件(swift的中间代码)(四讲)
- swift的底层实现机制可以通过lldb调试看到(比如swift对象内存分配的实现 ,在五中我们会展开讲解)
- swift对象的内存结构HeapObject(OC对象的内存结构为OC objc_object),swift(16字节)包含两个属性:MetaData +RefCount;OC(8字节)只包含一个属性:isa指针
三、补充知识
- frame varibale - L +变量名---->输出变量的内存地址
- 内存区域了解:(我们可用的区域是从0x00000-0x00007,大致可分为栈区和堆区,对于堆区又可分为全局区、常量区、代码区)
![]()
- po 输出值 p返回值的类型以及命令结果的引用名
- x/8g:读取内存中的值(8g代表8字节格式输出
- 引用好比一个在线文档
- 我们一般情况下都是用结构体,因为结构体在栈区分配较快
- 在lldn调试时,可使用cat address + 对象 即可探索地址所在空间
四、SIL文件
这部分内容我也是第一次了解,下面以swift的对象内存分配为例:
我们在项目的TARGTETS-XXScript-Build Phases的shell脚本中输出 swiftc -emit-sil ${SRCROOT}/LLSwiftTest/main.swift > ./main.sil & open main.sil 即可生成并打开SIL文件
五、lldb调试查看swift对象内存分配过程
在项目的debug-debug workflow-always show中会看到我们在生成一个类实例时调用的函数

通过对这行打断点按住control可继续向下进入这个函数调用内部
由此我们推断出:__allocating_init -----> swift_allocObject -----> swift_allocObject -----> swift_slowAlloc -----> Malloc(swfit对象内存分配过程)

浙公网安备 33010602011771号