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. wamp5设置外网访问方法

    1.安装完Wamp5之后,从外网访问网页时存在无法访问问题. 2.phpmyadmin外网没法访问 1.解决办法: 打开wamp的托盘图标(右下角),找到"Config files" ...

  2. JVM再了解了解

    转自 http://www.cnblogs.com/Coda/p/4331432.html 相信大家已经了解到Java具有跨平台的特性,可以“一次编译,到处运行”,在Windows下编写的程序,无需任 ...

  3. 嵌入式Linux驱动学习之路(五)u-boot启动流程分析

    这里说的u-boot启动流程,值得是从上电开机执行u-boot,到u-boot,到u-boot加载操作系统的过程.这一过程可以分为两个过程,各个阶段的功能如下. 第一阶段的功能: 硬件设备初始化. 加 ...

  4. 技术专题—Python黑客【优质内容聚合贴】

    作者:坏蛋链接:https://zhuanlan.zhihu.com/p/24645819来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 一.前言 本着知识分享,聚合优 ...

  5. Mysql占用过高CPU时的优化手段

    Mysql占用CPU过高的时候,该从哪些方面下手进行优化?占用CPU过高,可以做如下考虑:1)一般来讲,排除高并发的因素,还是要找到导致你CPU过高的哪几条在执行的SQL,show processli ...

  6. UML类图归纳

    作为一个程序员,掌握UML类图是开发和阅读程序的基础. 转载请注明地址http://www.cnblogs.com/zrtqsk/p/3739288.html,谢谢! 一.基本介绍 UML是一种标准的 ...

  7. 直流调速系统Modelica基本模型

    为了便于在OpenModelica进行仿真,形成一个完整的仿真模型,没有使用第三方的库,参照了DrModelica的例程,按照Modelica库的开源模型定义了所用的基本元件模型. 首先给出一些基本类 ...

  8. 处理 EF 并发其实就这么简单

    最近项目有点闲,终于可以了解点自己想了解的了,以前听同事讲面试的经历总会被问到“如何处理高并发大数据” 乍一听感觉这东西好像很有学问的样子,于是并发这个词在脑海里留深刻印像,而且在自己心中的技术地位也 ...

  9. DTCMS插件的制作实例电子资源管理(二)Admin后台页面编写

    总目录 插件目录结构(一) Admin后台页面编写(二) 前台模板页编写(三) URL重写(四) 本实例旨在以一个实际的项目中的例子来介绍如何在dtcms中制作插件,本系列文章非入门教程,部分逻辑实现 ...

  10. eclipse汉化全程

    在开始之前我说一下我的环境,eclipse版本eclipse-java-indigo-SR2-win32-x86_64,操作系统Win7,但是这个基本上没有影响.红字的那个注意一下,在下面需要根据这个 ...