Swift中的值类型与引用类型
本篇是研读Chris Eidhof, Ole Begemann, Airspeed Velocity著的《Swift 进阶》(对应Swift 5.6)所做下的笔记,可能掺杂了些许本人浅薄的思考,如有纰漏,恳请赐教。
值类型的本质
var a:Int = 3
var b = a
b += 1
a // 3
b // 4
值类型常/变量持有的是值。 当值类型变量a赋值给b时,拷贝的是值(严谨的说,由于写时复制特性,拷贝实际发生在b值修改时),也就是所谓的深拷贝
修改b的值并不会影响到a,他们是两个独立的值。
典型的值类型数据
- 基础数据类型(Int、Float、BOOL…)
- 字符串类型
- 集合类型(Array、Set、Dictionary)
- 结构体类型
- 枚举类型
引用类型的本质
var view1 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) var view2 = view1
view2.frame.origin = CGPoint(x: 50, y: 50)
view1.frame.origin // (50, 50)
view2.frame.origin // (50, 50)
引用类型常/变量持有的是实例的引用 当 view1 赋值给 view2 时,只是在 view1 指向的实例上新增了一个引用(浅拷贝),并没有发生新实例的创建,通过 view2 对实例进行修改会发现 view1 也发生了变化。
典型的引用类型数据
- 类
- 闭包、函数
可变性
class ScoreClass {
var home: Int
var guest: Int
init(home: Int, guest: Int) {
self.home = home
self.guest = guest
}
}
struct ScoreStruct {
var home: Int
var guest: Int
init(home: Int, guest: Int) {
self.home = home
self.guest = guest
}
}
let scoreStruct = ScoreStruct(home: 1, guest: 2)
let scoreClass = ScoreClass(home: 2, guest: 1)
scoreClass.guest = 3 // 可以修改
scoreStruct.guest = 2 // Cannot assign to property: 'scoreStruct' is a 'let' constant
如上例,你无法修改结构体常量的成员的值,即使成员是变量,而对象常量却可以这么做,因为结构体是值类型,结构体内部的数据理所当然也是值的一部分。
小结
引用类型变量的值,是一个指向实例的引用;而值类型变量的值,是值本身。 修改一个结构体的属性,即使修改的是多层的嵌套属性,都等同于给变量赋值一个全新 的结构体实例。
值类型传参与可变方法
func scoreClassGuestAdd1(_ score: ScoreClass) {
score.guest += 1 //可以修改
}
func scoreStructGuestAdd1(_ score: ScoreStruct) {
score.guest += 1 //Left side of mutating operator isn't mutable: 'score' is a 'let' constant
}
类似的,值类型在函数传参时,传递的同样是一个值的拷贝,同时swift默认将值类型参数设为了 let 类型(大概是为了提醒你修改这个参数不会影响到原本的值),而引用类型在传参时,传递的是引用,因此可以对实例进行修改。
而要对值类型参数进行修改,首先要保证传入的是 var 类型,其次需要在传参列表类型前加上 inout 关键字,并在函数调用时在参数前加上 & 符号,使值类型参数可以在函数内修改并同步到原来的值上,如下
func scoreGuestAdd1(_ score: inout ScoreStruct) {
score.guest += 1
}
scoreGuestAdd1(&scoreStruct)
需要注意的是,&符号并不像C或OC中那样代表取地址,swift仍然会在函数传参时拷贝值,只是当函数返回时,会额外将inout参数的值覆盖到原本的值上(即使函数内多次修改也只会触发一次属性观察)
extension ScoreStruct {
func scoreGuest() {
self.guest += 1 //Left side of mutating operator isn't mutable: 'self' is immutable
}
}
如上所示,结构体内用func定义普通方法是无法修改自身成员的,原因与之前的case类似,本质上是由于方法中隐式传入了self,函数内的self实际上已经是一个新值了,而想要在结构体方法中修改自身成员,需要用到可变方法
extension ScoreStruct {
mutating func scoreGuest() {
self.guest += 1
}
}
let scoreStruct = ScoreStruct(home: 1, guest: 2)
var scoreStruct2 = ScoreStruct(home: 0, guest: 0)
scoreStruct.scoreGuest() // Cannot use mutating member on immutable value: 'scoreStruct' is a 'let' constant
scoreStruct2.scoreGuest()
理所当然的,只有变量才能使用可变方法,因为可变方法实际上是对值做了修改。
内存管理(生命周期)
值类型的生命周期
值类型不受ARC管理,它的生命周期与作用域相关,当离开常/变量所在的作用域时内存就被释放了。
引用类型的生命周期
引用类型受ARC管理,当引用计数变为0或被设置为nil时,swift运行时都会调用对象的deinit方法并释放内存