Swift中有几个可以控制值的变化方式的机制.
本章会介绍这些不同的机制是如何工作的,以及如何区别值类型和引用类型,并证明为什么限制可变状态的使用是一个良好的理念.
1.变量和引用
Swift有两种初始化变量的方法,分别使用var和let关键字
var x: Int = 1
let y: Int = 2
使用let声明的变量被称为不可变量,而使用var声明的变量则被称为可变变量.
使用var声明的变量可以被修改
使用let声的变量无法被改变
x = 3 // 没问题
// y = 4 // 被编译器拒绝
2.值类型和引用类型
不可变性不止存在于变量声明中.
Swift的类型分为值类型和引用类型.
两者最典型的例子分别为结构体和类.
值类型与引用类型之间的关键区别: 当被赋以一个新值或是作为一个参数传递给函数时,值类型会被复制.
Swift几乎所有类型都是值类型,包括数组、字典、数值、布尔值、元祖和枚举,只有类是例外的。
struct PointStruct {
var x: Int
var y: Int
}
class PointClass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
值类型变量赋值后,被赋值的对象变了,变量不变.
引用类型变量赋值后,被复制的对象变了,变量跟着变了.
// 结构体是值类型
var structPoint = PointStruct(x: 1, y: 2)
var sameStructPoint = structPoint
sameStructPoint.x = 3
print("structPoint.x = \(structPoint.x), sameStructPoint.x = \(sameStructPoint.x)")
// structPoint.x = 1
// sameStructPoint.x = 3
// 类是引用类型
var classPoint = PointClass(x: 1, y: 2)
var sameClassPoint = classPoint
sameClassPoint.x = 3
print("classPoint.x = \(classPoint.x), sameClassPoint.x = \(sameClassPoint.x)")
// classPoint.x = 3
// sameClassPoint.x = 3
在调用函数的时候,值类型与引用类型的区别也同样是显而易见的.
func setStructToOrigin(point: PointStruct) -> PointStruct {
var tmpPoint = point
tmpPoint.x = 0
tmpPoint.y = 0
return tmpPoint
}
func setClassToOrigin(point: PointClass) -> PointClass {
point.x = 0
point.y = 0
return point
}
// 当被赋以一个新变量或传递给函数时,值类型总是会被复制,而引用类型并不会被复制.
var structOrigin: PointStruct = setStructToOrigin(point: structPoint)
print("structOrigin = \(structOrigin), structPoint = \(structPoint)")
// structOrigin = PointStruct(x: 0, y: 0), structPoint = PointStruct(x: 1, y: 2)
var classOrigin = setClassToOrigin(point: classPoint)
print("classOrigin.x = \(classOrigin.x), classPoint.x = \(classPoint.x)")
// classOrigin.x = 0, classPoint.x = 0
3.结构体与类: 究竟是否可变
在上述例子中,我们使用var而非let将所有的Point类型及它们的属性声明为可变变量.
现在我们需要结构体和类等构造出混合的类型,以及它们与var和let声明的相互作用进行一些说明.
let immutablePoint = PointStruct(x: 0, y: 0)
// immutablePoint = PointStruct(x: 1, y: 1) // 被拒绝
// immutablePoint.x = 3 // 被拒绝
var mutablePoint = PointStruct(x: 1, y: 1)
mutablePoint.x = 3
print(mutablePoint)
// mutablePoint.x = 3
使用let关键字声明结构体中的x和y属性,那么一旦初始化,我们将不能修改它们,无论保存这个点实例的变量可选的还是不可选的:
struct ImmutablePointStruce {
let x: Int
let y: Int
}
var immutablePoint2 = ImmutablePointStruce(x: 1, y: 1)
// immutablePoint.x = 3 // 被拒绝
print(immutablePoint2)
但是我们仍然可以给immutablePoint2赋一个新值
immutablePoint2 = ImmutablePointStruce(x: 2, y: 2)
print(immutablePoint2)
4.不可变性的好处
- 耦合度通常被用来描述代码各个独立部分之间彼此依赖的程度.耦合度是衡量软件构建好坏的重要因素之一.在很多面向对象语言中,对于方法,由于共享实例变量而产生耦合的情况十分常见.其结果是,修改变量的同时可能会改变类中方法的行为.这里就突出的不可变变量的优势.
- 引用透明像输入值相同则得到的输出值一定相同的函数.引用透明函数在它所存在的环境中是松耦合的:除了函数的参数,不存在任何隐式依赖的状态或变量.因此,引用透明函数更容易单独测试和理解.引用透明化在各个层面都使代码更加模块化.