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方法并释放内存


种一棵树最好的时间是在十年前,而后是现在。

Loading Disqus comments...
Table of Contents