第6章 不可变性的价值

220 阅读3分钟

github

Swift中有几个可以控制值的变化方式的机制.

本章会介绍这些不同的机制是如何工作的,以及如何区别值类型和引用类型,并证明为什么限制可变状态的使用是一个良好的理念.

1.变量和引用

Swift有两种初始化变量的方法,分别使用varlet关键字

    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类型及它们的属性声明为可变变量.

现在我们需要结构体等构造出混合的类型,以及它们与varlet声明的相互作用进行一些说明.

 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关键字声明结构体中的xy属性,那么一旦初始化,我们将不能修改它们,无论保存这个点实例的变量可选的还是不可选的:

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.不可变性的好处

  • 耦合度通常被用来描述代码各个独立部分之间彼此依赖的程度.耦合度是衡量软件构建好坏的重要因素之一.在很多面向对象语言中,对于方法,由于共享实例变量而产生耦合的情况十分常见.其结果是,修改变量的同时可能会改变类中方法的行为.这里就突出的不可变变量的优势.
  • 引用透明像输入值相同则得到的输出值一定相同的函数.引用透明函数在它所存在的环境中是松耦合的:除了函数的参数,不存在任何隐式依赖的状态或变量.因此,引用透明函数更容易单独测试和理解.引用透明化在各个层面都使代码更加模块化.