Swift 里的结构体非常特殊。

类是面向对象编程语言中传统的结构单元。和结构体相比,Swift 的类支持实现继承,(受限的)反射,析构函数和多所有者。

既然类比结构体强大这么多,为什么还要使用结构体?正是因为它的使用范围受限,使得结构体在构建代码块 (blocks) 的时候非常灵活。

值类型和引用类型

结构体是值类型的,而类是引用类型的,这一行为上的细微区别造就了架构上的无限可能。

值类型的实例,不管是在赋值或是作为函数参数的时候,都是被复制的。数字,字符串,数组,字典,枚举,元组和结构体都是值类型。比如:

var a = "Hello"
var b = a
b.extend(", world")
println("a: \(a); b: \(b)") // a: Hello; b: Hello, world

引用类型的实例 (主要是类) 可以有多个所有者。将一个引用赋值给一个新的变量或者传递给一个函数的时候,它们都指向同一个实例。这是你熟悉的对象的行为。比如:

var a = UIView()
var b = a
b.alpha = 0.5
println("a: \(a.alpha); b: \(b.alpha)") // a: 0.5; b: 0.5

这两种类型的区别看起来似乎不大,但是选择值类型还是选择引用类型会给你的系统架构带来很大的差异。

我们在代码中引用对象和我们在现实生活中引用对象是一样的。编程书籍经常使用一个现实世界的隐喻来教授人们面向对象编程:你可以创建一个 Dog 类,然后将它实例化来定义 fido (狗的名字)。如果你将 fido 在系统的不同部分之间传递,它们谈论的仍然是同一个 fido。这是有意义的,因为如果你的确有一只叫 Fido 的狗,无论何时你谈到它时,你将会使用它的名字进行信息传输 —— 而不是传输狗本身。你可能依赖于其他人知道 Fido 是谁。当你使用对象的时候,你是在系统内传递着实例的名字

值就像数据一样。如果你向别人发出了一张费用开销表,你发出的不是一个代表那个信息的标签 —— 你是在传递信息本身。消息接收者可以在不和任何人交流的情况下,计算总和,或者把费用写下来供日后查阅。如果消息接收者打印了费用表并且修改了它们,这也没有修改你自己的那张表。

一个值可以是一个数字,也许代表一个价格,或是一个类似字符串的描述。它可以是枚举中的一个选项:这次的花费是因为一顿晚餐,还是旅行,还是材料?在指定的位置中还能包括一些其他的值,比如一个代表经度和纬度的 CLLocationCoordinate2D 结构体。或者它可以是一些其他值的列表等等。

Fido 可能在自己的地盘里来回跑叫。它也许会有特殊的行为使它区别于其他的狗。他可能会同其他的狗建立关系。你不能把 Fido 换成其他的狗 —— 你的孩子们会发现的!但是一张费用开销表是独立的。那些字符串和数字不会做任何事情。它们不会背着你私下改变,不管你用多少种不同的方式在第一列写入了一个6,它永远只会是一个6

这就是值类型的伟大之处。

值类型的优势

Objective-C 和 C 具有值类型,但是 Swift 允许你在以前不能使用的场景下使用它们。比如,泛型系统的抽象特性可以让泛型类型在值和引用类型间互换。数组既可以存储 Int 也能存储 UIView。Swift 中的枚举的表现更是大放异彩,因为它们现在可以携带某些值和方法了。结构体可以遵守协议和指定的方法。

Swift 增强了对值类型的支持,这提供了一个巨大的机会:值类型成为了使代码简单的一个非常灵活的工具。你可以使用它们将孤立的、可预见组件从臃肿的类中抽离出来。默认情况下,值类型被强制使用或者至少说被鼓励使用在属性上,来使得工作更清晰。

在这部分,我会描述一些鼓励使用值类型特性的情形。值得注意的是,你也可以让对象包含这些特性,但是语言本身决定了你没必要去那么做。如果你在代码里看到了一个对象,你不会期待它出现这些特性;然而,如果你看到了一个值类型,那么对这些特性的期望就是合理的。诚然,不是所有的值类型都有这些属性 —— 我们稍后会讨论这个 —— 但是这是合理的概括。

值类型是稳定的

总的来说,值类型不具有行为。它是非常稳定的。它保存数据并暴露使用这些数据进行计算的方法。其中的一些方法可能会使值类型本身发生改变,但是控制流却还是严格地受控于该实例的唯一所有者。

这太好了!这下更容易思考被唯一所有者直接调用才会执行的代码了。

相比之下,一个对象可能将它自己注册为一个定时器的 target。它可能会接收到来自系统的事件。这样的交互意味着引用类型需要有多个拥有者。因为值类型只能有一个所有者并且没有析构函数,所以我们也不容易写出会对自己产生副作用影响的值类型。

值类型是孤立的

一个典型的值类型对任何外部组件的行为都没有隐式的依赖。一眼看上去,与引用类型和其未知个数的所有者之间的交互相比,值类型和它的唯一所有者之间的交互要简单多了。它是孤立的。

如果你正在获取一个可变实例的引用,那么你对该实例的所有其他所有者都产生了隐式依赖:它们可能在任何时刻背着你偷偷改变它。

值类型是可交换的

因为每次将值类型赋给一个新变量的时候,该值类型都是被复制的,所以,所有的这些副本都是可交换的。

你可以安全地存储传递给你的值,然后在将来就像使用值一样使用它们。人们区分该实例和其他实例的唯一依据就是实例所包含的数据。可交换还意味着不管一个给定的值是如何进行构造的,我们通过 == 进行比较的话,同样的值在任何情形下都是相等的。

所以如果你使用值类型同系统里的组件进行通信,你可以很容易地改变你的组件图。你有没有一个视图用来描绘触摸采样的序列?你不用触及视图代码,只通过一个触摸采样序列的组件,就可以补偿触摸延迟,依据前一次的采样,追加用户手指将要移动位置的预测,然后返回一个新的序列。你可以自信地将另一个新的组件的输出传给视图 —— 因为视图分辨不出区别。

为值类型编写单元测试不需要花哨的模拟 (mocking) 框架。你可以直接从应用程序中的活的实例中构造出无分别的值。上面提到的触摸预测组件很容易进行单元测试:可预测的值类型输入,可预测的值类型输出等,它们都不会产生副作用。

这是巨大的优势。在以对象行为主导的传统架构中,你必须要测试与正被测试的对象的交互以及与系统的其他部分之间的交互。那通常意味着笨拙的模拟,或者为了建立那样的关系而添加了大量的设置代码。值类型是孤立的,稳定的和可交换的,所以你可以直接地构建一个值,调用一个方法,然后检查输出。更简单的测试,更大的覆盖范围意味着代码更容易修改。

不是所有的值类型都有这些特性

虽然值类型的结构鼓励这些特性,但是你也可以使值类型违反这些特性。

包含不是由所有者调用而执行的代码的值类型,通常是不可预测的,并且通常情况下应该是要避免使用的。比如:一个结构体的构造函数可能调用 dispatch_after 来安排一些工作。但是将该结构体的一个实例传递给函数的时候,因为进行了一次复制,就会不经意地重复做这件事情。值类型应该是稳定的。

包含引用的值类型通常都不是孤立的,并且应该避免使用它们:它们携带了对那个对象的所有其他所有者的依赖。这些值类型也不是易交换的,因为外部引用可能以复杂的方式与系统的其他部分相联系。

对象们的对象

我当然不是建议使用稳定的值类型来构建所有的事情。

更精确地讲,对象也是有用的,因为它们不包含我上面所说的属性。一个对象在系统中扮演着实体的角色。它有身份,具有行为,通常也是独立的。

那种行为通常复杂并且不容易思考,但是其中一些细节通常可以由简单的值和孤立地函数调用表现出来。那些细节不会和对象的复杂的行为交织在一起。通过将它们分离,对象的行为就会变得清晰。

可以将对象看成是一个薄的、命令式的层,它位于可预测的、纯值类型的层之上。

对象维护通过值来定义的状态,但是那些值其实是独立于对象被设定和操作的。值层 (value layer) 实际上没有状态;它仅仅用来表示和变换数据。那些数据作为状态可能有 (也可能没有) 高层的意味,这取决于使用值的上下文。

对象就像 I/O 和网络一样会有副作用,但是数据、计算和重要的决策最后都驱使这些副作用存在于值类型层。对象就像薄膜,通过这一层薄膜,将那些纯净的、可预测的结果引入副作用的不纯净的领域。

对象可以和其他对象通信,但是通常它们发送的是值,而不是引用,除非它们确实想要和外部不可或缺的层创建一个持久的连接。

值类型的总结

值类型能够使你构建非常清晰,简单,更容易测试的典型架构。

值类型与外部状态通常没有依赖或者只有很少的依赖,所以当你思考它们的时候,你只需要考虑很少的一部分。

值类型是内在可组合的和可重用的,因为它们是可交换的。

最后,一个值类型层允许你从应用程序稳定的业务逻辑中独立出活跃的行为元素。代码越稳定,你的系统会变得越容易测试和修改。


话题 #16 下的更多文章

swift中的结构体和枚举的更多相关文章

  1. Swift类和结构体

    在C++中,相信不会有太多人去详细考究结构体和类的区别,因为二者关系实在不大.但在Swift中,结构体和类的关系非常大,它们的组成部分都包括:初始化器.实例方法.实例属性.类型属性.类型方法等等:二者 ...

  2. swift学习笔记3——类、结构体、枚举

    之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...

  3. swift 类 与 结构体

    这两天突然有人问我  swift里面 类和 结构体  有什么区别? 说实在的本人目前不太看好swift,相信很多人也是,oc 都 很成熟了. 本人目前不打算深入了解swift的原因swift  语言 ...

  4. Swift: 类与结构体

    对比类与结构体 类与结构体有许多的相同点,它们都可以: 定义属性来存储值: 定义方法来提供功能: 定义下标操作: 定义初始化函数: 扩展它的默认的实现: 遵从协议: 类有一些额外的能力,但是结构体没有 ...

  5. C#语言基础——结构体和枚举类型

    结构体和枚举类型 一.结构体(struct) 结构类型是用户自己定义的一种类型,它是由其他类型组合而成的,可包含构造函数.常数.字段.方法.属性.索引器.运算符.事件和嵌套类型的值类型.结构在几个重要 ...

  6. Swift入门篇-结构体

    前面主要是介绍swift语言中基本类型的用法,今天给大家介绍的是swift的结构体的用法,swift中结构体的用法和其他语言的用法,还有不太一样,不过您多敲几遍,就可以理解结构体,结构体在ios开发中 ...

  7. c# 函数练习;结构体、枚举类型

       * 结构体 1.就是一个自定义的集合,里面可以放各种类型的元素,用法大体跟集合一样. 注意:枚举类型和结构体都属于值类型. 2.定义的方法: struct student { public in ...

  8. iOS开发——C篇&结构体与枚举

    一:结构体与枚举的介绍: 结构体与枚举:是一种存储复杂的数据结构体:是用户自定义的一种类型,不同类型的集合,而数组是相同类型变量的集合. 二:结构体的创建 struct user {     char ...

  9. C语言基础(19)-结构体,联合体,枚举和typedef

    一.结构体 1.1 结构体struct定义及初始化 #include <stdio.h> // 这个头文件在系统目录下 #include <stdlib.h> // 使用了sy ...

随机推荐

  1. Visio连接数据表实体外键[快捷记录]

    打开数据库模型图. 单击“常用”工具栏上的“连接线”工具. 将“连接线”工具放在父表的中心上,使表的四周出现轮廓线,然后拖到子表的中心.当子表出现轮廓线时,松开鼠标按钮. 两个连接点均变为红色,同时父 ...

  2. css3动画的两种方式transition和@keyframs

  3. HTML 学习笔记 CSS(选择器)

    CSS元素选择器 最常见的CSS 选择器就是元素选择器 换句话说 文档的元素就是最基本的选择器 如果设置HTML样式 选择器通常就是某个HTML元素 比如p h1 em a 甚至可以是HTML本身 h ...

  4. web cache server方案比较:varnish、squid、nginx

    linux运维中,web cache server方案的部署是一个很重要的环节,选择也有很多种比如:varnish.squid.nginx.下面就对当下常用的这几个web cache server做一 ...

  5. listview1

    Edit1.Text := listview1.Items[i].Caption; //读第i行第1列 Edit2.Text := listview1.Items[i].SubItems.string ...

  6. php常见问题

    1,新安装的lamp在打开php文件的时候出现access forbid问题,这个出现的原因是directory的路径权限问题,解决方法 将httpd.conf中的 <Directory /&g ...

  7. [转]nodejs npm常用命令

    FROM : http://www.cnblogs.com/linjiqin/p/3765772.html npm是一个node包管理和分发工具,已经成为了非官方的发布node模块(包)的标准.有了n ...

  8. AI: Jarvis

    AI: Jarvis   扎克伯格本周二在facebook发布了一篇文章,介绍自己利用个人时间开发的一套在自己家里使用的AI系统,并将它命名为Jarvis,对!就是电影钢铁侠里的AI助手Jarvis. ...

  9. Android — Camera聚焦流程

    原文  http://www.cnphp6.com/archives/65098 主题 Android Camera.java autoFocus()聚焦回调函数 @Override public v ...

  10. c++ 头文件

    可以将程序分为二部分: 头文件:包含结构声明和使用这些结构的函数的原型 源代码文件: 包含与结构有关的函数的代码 不要将函数的定义或变量的声明放在头文件里, 一般头文件可以包含以下内容 >函数原 ...