Swift—Struct和Class的区别与应用
1.Struct 与 Class 的区别?
关于 Swift 中 Struct 和 Class 的区别,我认为核心可以从三个方面来理解:本质类型、内存管理和继承能力
第一,也是最根本的区别,是它们的『本质类型』不同。
- Struct 是『值类型』。当我把一个 struct 变量赋值给另一个变量,或者将它传入函数时,实际上是创建了一份完整的、独立的副本。修改这个新的副本,完全不会影响原始的数据。(var a = SomeStruct(),然后 var b = a,此时 a 和 b 在内存中是两份独立的数据。改 b 的属性,a 不会变)
- Class 是『引用类型』。当我将一个 class 实例赋值给另一个变量时,我只是传递了一个对同一块内存地址的引用(或者说指针)。这两个变量实际上指向的是同一个实例。(举个例子,var a = SomeClass(),然后 var b = a,此时 a 和 b 指向同一个实例。无论通过 a 还是 b 去修改属性,另一个也会看到变化)
第二,这个本质区别直接导致了它们在『内存管理』上的不同。
- Struct 的内存管理非常简单直接。因为它通常是存储在栈上,创建和销毁都由系统自动完成,开销小,速度非常快。它的生命周期随着作用域结束而结束。
- Class 的内存管理则相对复杂,它使用的是 ARC(自动引用计数)。因为实例存储在堆上,系统需要通过计数来知道什么时候可以释放内存。这就引入了『循环引用』的风险,需要我们开发者使用 weak 或 unowned 来手动打破,否则就会导致内存泄漏。而 Struct 基本没有这个问题。
第三,在『继承能力』上,它们也不同。
- Class 支持继承。这意味着一个类可以继承另一个类的特性,这是面向对象编程的基石。如果需要构建一个有清晰层次关系的模型,Class 是天然的选择。(UIView -> UIScrollView -> UITableView)
- Struct 不支持继承。但它们可以通过遵守『协议』和使用『协议扩展』来获得多态的能力,这也就是 Swift 非常推崇的“面向协议编程”(POP)范式。(绘制图像的时候定义shape的协议,让后面的struct遵循协议,还可以进行扩展)(如果只是计算和绘制,结构体+协议是更好的选择。如果需有复杂的对象关系,或者需要管理生命周期,才考虑使用类。如果对象之间的关系可以用"包含"、"使用"来描述,用结构体;如果需要用"拥有"、"管理"、"协调"来描述,考虑用类。)
最后,Class 有析构函数 deinit,可以在实例被销毁时执行一些清理工作,而 Struct 没有。
2.什么时候用 Struct,什么时候用 Class?
首先判断语义,需要值类型还是引用类型。然后从功能进行区分。
struct:
1.简单数据模型 - 比如用户信息、商品信息、坐标点等,主要用来存储数据的场景
2. 配置参数 - 比如网络请求配置、页面配置,不需要共享状态的配置信息
class:
1. 需要共享和可变状态 - 比如全局的状态管理器,多个地方需要访问和修改同一个实例(购物车)
2. 需要继承 - 比如自定义UI控件(UIKit内的控件)
3. 需要与OC交互 - 必须使用Class并通过@objc标记(oc无法识别struct)
3.值类型就一定在栈上吗?
说法不准确。值类型的存储位置分几种情况:
在栈上:
-
小的、临时的结构体(函数内部创建的点坐标结构体)
在堆上:
-
被类持有 - 当结构体作为类的属性时,会随着类实例分配在堆上(VC里有个
userInfo: UserStruct属性,这个结构体随着 ViewController 实例分配在堆上) -
被闭包捕获 - 当结构体被闭包捕获时,会被提升到堆上以保证生命周期(闭包本身是引用类型,网络请求的回调闭包中捕获了一个配置结构体时)
-
大结构体 - 当结构体较大时,系统会自动在堆上分配(包含几千个元素的数组结构体)
核心要点:值类型的内存位置是动态的,Swift会根据使用场景自动优化,不能简单地认为值类型就在栈上。
下面乱死了,不知道自己当时写的啥。留着吧
值类型vs引用类型
值类型优化机制COW
Struct 结构体
mutating 方法内修改外部属性
static 类属性
值类型的构造函数委托
调用其他构造函数执行部分实例的初始化
Class 类
override 继承后复写 final 防止复写
super 访问父类成员
static/class 类属性
特征运算符 判断是否引用自同一个实例(===)(!==)
属性
存储属性 变量与常量
计算属性 计算得来getter setter(可选)
类属性 整个类共有 static 不和实例关联
懒加载属性 真正使用时才加载到内存 lazyvar
属性监听 willSet didSet
继承与重写
self属性 在类中代表实例
类类型的构造函数委托
指定构造函数(designated initializer) 完整参数初始化
便捷构造函数(convenience initializer)提供 “简化版” 初始化方式
指定构造函数必须总是向上委托,便捷构造函数必须总是横向委托

class Car {
var speed:Float
init(speed:Float){
self.speed = speed
}
convenience init(){
self.init(speed: 120.0) //传值,便捷构造函数调用指定构造函数
}
}
class Bus:Car {
var name:String
init(name:String){
self.name = name //初始化子类
super .init(speed: 120.0) //初始化父类
}
convenience init(){
self.init(name: "默认") //提供默认值
}
}
构造函数继承与重写
1.子类没有定义指定构造函数,自动继承父类的
2.子类提供了所有父类指定构造函数的实现,自动继承所有父类便捷初始化器
初始化(构造)
默认 init()
自定义 init(参数)
更多推荐



所有评论(0)