Swift - 不可变性的价值

734 阅读4分钟

很久就计划开始学习Swift,一直未付出行动。原因有两点,一个是人惰性,二是因为担心写不好。挣扎了好几天,终于鼓起勇气与动力,写第一篇关于Swift的知识点。如果有不足之处,望请致电。 开篇讨论下Swift中的可变性或不可变性的区别。

Swift中变量的不可变性

Swift对于不可变性可控制通过letvar关键字来控制。下面我们来看看这两个关键字的区别:

    let x: Int = 10
    var y: Int = 20

当我们使用letvar申明了变量时,它们最大区别是:

  1. let申明的变量称为不可变变量,不可再赋予新值,如果赋予新值时会报编译错误“Cannot assign to value: '' is a 'let' constant”.
  2. var申明的变量称为可变变量,可以随意赋予新值,前提只要类型一样则可以。

这样实现机制,最大的好处是,使用let可以减少更多的可变因子,我们可以将更多的精力放在那些可变变量。当我们看到一个变量是let申明的时候,我们只需要关心它的初始值,不用担心它在后续的使用中值会发生更改。

虽然在Objective-C中,为了实现类似功能,分别设计了可变类型和不可变类型,例如NSArray和NSMutableArray、NSString和NSMutableString、NSDictionary和NSMutableDicionary、NSSet和NSMutableSet等等。虽然如此,但是在使用是需要使用不同类型,相对繁琐。即使是一个不可变对象,但是也不是真正意义上的不可变,我们可以在后续中修改它的引用指向从而修改它的值。例如: src = "123"。还可以通过performSelector:withObject:等方法修改对象的值,例如:

    NSString *src = [NSMutableString stringWithString:@"我是可变字符串"];
    NSString *suffix = @"123";
    [src performSelector: **@selector**(appendString:) withObject:suffix]; // src的值是"我是可变字符串123"

虽然一般逻辑下没有人会这么操作,但这段代码可告诉我们Objective-C的不可变性不是那么值的信任。如果在将src传入到SDK的函数且看不到源码,刚好通过NSString和NSMutableString来做某些逻辑的话,那么你会在很诧异src在那里修改的值,让你头疼很长一段时间。

Swift的值类型和引用类型

在Swift中不是只有变量才具有不可变性和可变性,类型也分为值类型和引用类型。值类型包含结构体、枚举、Int、String、Double、Bool等,引用类型则是使用class标记的类型。为了讨论值类型和引用类型的区别,我这里使用结构体和类来做例子。 我们先来定义一个结构体和类:

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 point1 = PointStruct(x: 1, y: 1)
    var point2 = point1
    point2.x = 2

    var point3 = PointClass(x: 3, y: 3)
    var point4 = point3
    point4.x = 10

执行上面的代码后:

  1. 结构体对象的 point1 = (1, 1), point2 = (2, 1), point1的值没有改变,而类对象的 point3 = point4 = (10, 3)。这就是值类型和类类型的最大区别,当值类型在赋值给一个新的变量或作为参数传递时,值类型会被复制。所以在给point2的x赋值时,point1的值不会受到影响,正是在point2 = point1赋值时发生了值赋值,可以用OC中的深拷贝来理解。
  2. 而类对象point4.x = 10在赋值后,point3的x值也更改了。因为point4 = point3赋值时是指针引用,point4指向的还是point3这个变量,像我们生活中的别名、外号,可以用OC中的浅拷贝来理解。

Struct中let 和 var的区别:

    let pointClass = PointClass(x: 4, y: 4)
    pointClass.x = 8
    
    let pointStruct = PointStruct(x: 2, y: 2)
    pointStruct.x = 4

截屏2021-12-18 17.41.56.png 我们看到Struct申明为let时修改其var属性会报错:Cannot assign to property: 'pointStruct' is a 'let' constant,意思是不能将pointStruct设置为let不可变。 因为我在后续修改了pointStruct的值: pointStruct.x = 4。但是引用类型却不会。

这就是值类型不可变性与引用类型的最大区别: 当值类型为不可变时,其所有属性都为不可变(即使在声明为var);但是引用类型的var属性却不受其对象是否声明为let或其他的。

理解值类型和类类型的区别极为重要,这样我们可以预测赋值行为将会如何修改数据,同时可以确定那一部分代码会受到影响。值类型的赋值,不会影响原来的数据;类类型赋值时指向同一个变量地址,会相互影响。当我们不希望修改原值时,使用值类型,否则使用类类型。

参考: Chris Eidhof. “函数式 Swift”