使用值类型构建更好的 Swift 应用程序

  • Sergio De Simone
  • 陆志伟

2015 年 8 月 6 日

话题:语言 & 开发架构

在 WWDC2015 上,苹果工程师 Doug Gregor 和 Bill Dudney评价了 Swift 对值类型的支持,并解释了如何通过它实现一种灵活的不可变性,以构建更好的应用。

Gregor 首先评价了 Objective-C 中常用的引用语义。引用语义的主要问题在于它有可能产生预期之外的对象共享行为,这种行为有可能导致对象的属性被暗中改动。Objective-C 程序员对此已经十分了解了,因为许多 Cocoa 和 Cocoa Touch 类,像 NSString、NSURLRequest,以及所有的集合调用时都需要拷贝。所以为了让程序员更易于进行防御性的拷贝,Objective-C 语言甚至为属性提供了一个 copy 特性,当给一个给定的属性分配对象时,运行时本身就会拷贝一个副本。

对于性能和内存的使用来说,防御性拷贝明显不是最优的措施,尤其在使用不当时更可能造成微妙的问题。

随后,Gregor 继续探索不可变性是否是这个问题的正确答案。许多 Cocoa 类,像 NSDate、NSURL、UIImage、NSNumber 和其它许多类都强制实施不可变性。根据 Gregor 的评价,不可变性拥有许多优点,比如没有副作用和可共享,但它也会生成尴尬的接口,并且不能有效地映射到机器模型。为了具体说明这一点,Gregor 在 Haskell 和 Swift 中分别运行 Eratosthenes 筛法来举例说明不可变性是如何牺牲性能的。

根据 Gregor 的评价,对值语义的使用正是这类问题的解决办法,而 Swift 完全支持这种值语义:

  • 所有的基本类型,比如 Int、Double、String 等都是值类型;
  • Swift 中的所有集合类型,比如 Array、Set 和 Dictionary 都是值类型;
  • 值类型可以进行组合,比如 tuples、structs 和 enums 仅包含值类型,因而它们自身也是值类型,从而可以根据值语义建立抽象类型。

值类型仅仅就是值,并且它们是不可变的。它们没有标识符,所以只能以值来区分。这就需要所有的值类型都遵守 Equatable 协议。

protocol Equatable {
    /// Reflexive - `x == x` is `true`
    /// Symmetric - `x == y` then `y == x`
    /// Transitive - `x == y` and `y == z` then `x == z`
   func ==(lhs:Self, rhs:Self) -> Bool
}

此外,值类型允许应用程序根据需要在可变性和不可变性之间取得适当的平衡。Gregor 表示:实际上,你可以对值类型使用 let 关键字,以指定一个不会改变的变量,或者使用 var 来指定一个可以在不影响其它值的前提下更新自身的值的变量。

let numbers = [1, 2, 3, 4]
var strings = [String]()
for x in numbers {
    strings.append(String(x))
}

如前文所述,值类型是不可变的,所以它们只能被拷贝。但是,Gregor 说,它们的拷贝操作开销很低,对于简单的值类型,比如 Int、Double、CGPoint 等, 它们的拷贝时间是个常数。对于可扩展的数据结构,将会使用写时拷贝(copy-on-write)技术,只有当值改变时,才会建立一个副本,所以这种技术比默认拷贝方式更有效率。

此次演讲的第二部分专注于提供一个使用 Swift 值类型进行编程的动手实验示例。

查看英文原文:Building Better Swift Apps Using Value Types

语言 & 开发架构