-
Swift 新功能
了解 Swift 的最新进步,这是一种安全、快速且表现力强的语言。探索在构建时间、代码大小和运行时性能方面的改进。了解如何在代码中利用这些新功能来消除样板代码、提高安全性和提升开发工作的整体效率。
资源
相关视频
WWDC18
-
搜索此视频…
上午好 欢迎参加 Swift 新功能介绍会议 今年对于 Swift 和 Swift 社区来说 发生了许多激动人心的事 接下来的 40 分钟里 Slava 和我将很荣幸 向大家介绍一系列最新进展
本会议大致 分为两部分 首先 我会介绍 Swift 开源计划 以及整个社区的新情况 接下来 我们会探讨 Swift 4.2 Swift 4.2 将从今天起 在 Xcode 10 测试版中提供预览 2015 年年底开始 Swift 作为开源项目登陆 GitHub
用户社区十分活跃 大家展开讨论 提交新功能 审核后将新功能 加入语言和标准库中
在此期间 有 600 人 向 GitHub 上的 Swift 开源项目 贡献了代码 总共合并了 18000 多项合并请求
自从 Swift.org 上线以来 Swift 便可以在 Swift.org 上下载 作为可下载工具链用于 Xcode 以及多个版本的 Ubuntu 其中既包括开发快照 也包括正式版本
我们希望 Swift 得到所有平台的支持 方便大家使用 重要工作之一 就是拓展社区的 测试支持 许多开源项目用户 也在努力让 Swift 支持 其他平台 我们也想要助一臂之力
大约一个月前 我们 扩展了公共持续集成系统 以支持社区设立的 持续集成节点 如果你是社区的一员 想要让 Swift 得到其他平台的支持 你现在可以 直接接入你的硬件支持 并开始测试 这一前提可以帮助 Swift 得到 其他平台的支持
我们还投入了很多 建立了 Swift 开源项目的 周边社区 在这里 社区成员可以讨论语言中的所有变化
大约六个月前 我们 不再使用邮件列表 因为信息量太大 而改用论坛 这是当时社区的呼声 起初有很多人想要以适合 自己的方式真正 参与项目 但发现困难重重 有了论坛 你们就可以 以适合自己的 方式参与项目 论坛非常成功 我们也想拓展 它的用途 支持 整个开源项目 如果你在维护 一个开源项目 例如 很受欢迎的 Swift 库 你就可以用论坛 来讨论项目内容 例如与用户沟通 或讨论开发进度
我们还想 拓展 Swift.org 的作用 让它能为整个社区所用 本周 我们将把 Swift 编程语言手册 迁移到 Swift.org
地址为 docs.swift.org 我们今后也会在这里 公布更多文档 供社区使用 关于 Swift 最激动人心的是 人们很喜欢使用 Swift 并且在很多场合 讨论 Swift 例如博客 聚会 还有会议 而 Apple 认为 我们很有必要 参与其中 因为讨论就是在这些地方发生 过去一年间 我们格外重视 参与这些会议 并且进行了技术展示 包括我们在 Swift 上的 工作 Swift 的工作原理 还有你们参与 开源项目的方式 这些我们在今后 也会致力于向前推进
其中一项重要努力 是周五在 WWDC 场地附近举办的 try Swift 圣何塞会议 届时会有工作坊 成员来自社区 帮助有兴趣 为 Swift 开源项目做贡献的 与会人员 届时 Apple 的 编译器团队也会出席 帮助大家进行交流 以上就是关于社区的新进展 我们来谈谈 Swift 4.2 我认为 首先应当谈谈 这个版本的 内容 以及在整体发展过程中的地位
Swift 更新 特别是大版本更新 大约 两年一次 Swift 4.2 是继 Swift 4.1 和 4.0 之后的大版本更新
总体而言 这个版本 有两大要点 第一是进一步关注 开发者效率 你们可以在很多方面 感受到 项目构建速度更快 但整体而言 核心工具的体验 也大幅改善 例如调试器(Debugger)和编辑器(Editor) Swift 社区也 也不断重视改进语言 从而改善一般的 开发者工作流 去除鸡肋代码 Apple 也一直在 改进 SDK 从而 让 Objective-C API 更好地 映射到 Swift 中 改善对语言 和 API 的运用
另一方面 语言底层得到 大幅改善 运行时间也有很多变化 以实现二进制兼容性 这一功能将在 Swift 5 中推出 预计于 2019 年早些时候发布
什么是二进制兼容性 二进制兼容性指 构建 Swift 代码时 你可以使用 Swift 5 的编译器和层 而在二进制层面 代码可以与 同一编译器或其他编译器层 构建的代码 相互操作 这一点十分重要 标志着这门语言 走向成熟 有了这一功能 Apple 就可以将 Swift 运行时 移入操作系统 App 可以直接使用它 而不再需要 将其放入 App 包中 这样可以缩减代码体积 但同样重要的是 启动时间和 内存使用也能得到优化 对社区而言帮助很大 我们也会 公布实现 ABI 稳定性或二进制兼容性的 相关进展 可以访问 Swift.org 的 ABI 稳定性总览页面 跟踪进展
今天的重点是 Swift 4.2 这是实现 Swift 5 的 重要一步 我们来谈谈 源兼容性
像 Xcode 9 一样 Xcode 10 只搭载一种 Swift 编译器 如果你使用 Xcode 10 你就在使用 Swift 4.2 但就像 Xcode 9 一样 这一编译器也支持 多种语言 兼容性模式 在所有模式中 你都可以使用新 API 可以使用全部新特性 这足以带来 影响代码源的变化 前两个模式 在 Xcode 9 中就已存在 保留它们是为了提供 开箱即用式的体验 你可以直接构建 Swift 3 和 Swift 4 代码 不需修改 Swift 4.2 模式与 Swift 4 模式基本相同 但包含刚才提到的 SDK 改进
就这样 如果使用之前版本的 Xcode 可以使用迁移帮助(Migrator Support) 该功能位于编辑(Edit)菜单 用来自动进行大部分修改
我想先做一项 关于 Swift 4.2 SDK 变化的 免责声明 后期的 Xcode 10 测试版可能 会进一步改进 SDK 这是为了能够 整合大家 关于 API 改进方向 和改进如何反映在 Swift 中 等反馈意见 这意味着 如果你提前 迁移到 Swift 4.2 就要注意后续可能出现的变化 你也可以先不迁移 之后再选择迁移 完全取决于你
Swift 4.2 中 我们在加速让 Swift 代码成型 并面向未来 这是语言成熟的重要阶段 因此 我们认为 大家都有必要 从 Swift 3 迁移到 Swift 4.2 代码体积得到了重要改善 语言整体也 有所改进 Xcode 10 将会是 最后一个支持 Swift 3 兼容模式的版本 我们来谈谈工具 方面的改进
在 Platform State of the Union 会议中 我们提到 Xcode 9 中的 Swift 项目构建 也得到了显著改善 这些结果是在一台配备 四核 i7 处理器的 MacBook Pro 上得到的 我们来仔细看看其中之一
这个项目混合了 Objective-C 和 Swift 代码 它最初是一个 Objective-C 项目 随后 开始采用 Swift 这种情况很常见 屏幕上的构建时间 提升无法反映 构建这段 Swift 代码 到底比之前快了多少 如果我们只关注 Swift 构建速度的提升 构建速度实际上快了三倍 这就是为什么 项目速度提升只有 1.6 倍
可以看到 构建速度提升 整体取决于 项目性质 使用多少 Swift 代码 以及处理器的 核数 但实际情况中 许多项目都提速多达两倍 提升主要由于 Swift 目标 具有跨文件 可见性 这是 Swift 的优异特性之一 不需要头文件 过去编译器会执行 许多冗余工作 于是 我们就 重新设计了编译 流程 大幅减少了 冗余 并且更好的利用处理器 你机器上的核 因此才会有这样的速度提升 欲知更多细节 本周晚些时候 会有两场讲座 深入分析构建过程 背后的工作原理 会提到 更多细节 解释 为何会有如此性能提升
性能大幅提升是由于 调试版本构建的改进
我想重点讲这一功能 为何出现在 Xcode 的构建选项中 最近我们将 编译模式(Compilation Mode)从 优化水平(Optimization Level)中分离出来 编译模式决定 项目构建的方式 如果是构建发行版本(Release) 默认设置是全模组 编译(Whole Module Compilation) 目标中全部文件 总是一起构建 进行优化的 可能性最高 重点不是优化的数量 而是 进行优化的可能性 而如果是调试版本(Debug) 则是增量(Incremental)构建 并非所有文件 都会构建 或者再构建 这是性能表现 和构建时间权衡的结果 调试版本构建的 优化水平默认仍然是 无优化(No optimization) 这样构建速度更快 调试信息更全 发行版本构建则着重优化速度 我们稍后会再提到优化水平
好的 分离编译模式和 优化水平 反映了一个非常有趣的权宜之计 有人发现有时候 如果 结合全模组 编译并且无优化时 可以更快的 构建调试版本 这是因为 这一组合显著减少了 我们现在努力 消除或大幅减少的冗余工作
而这样组合的问题在于 它会减慢 增量构建的速度 只要你修改了目标中的 一个文件 整个目标就要重新构建 而 Xcode 10 改进了 调试版本构建 我们认为 你们不再需要 使用这种权宜之计 我们也 发现 默认的 增量构建 表现 同这种组合相当 甚至更好 特别是现在 增量构建得到了正式支持
我们来谈谈 运行时得到的 底层优化 这也是 实现二进制兼容的 环节之一
Swift 使用自动内存 管理 并且像 Objective-C 一样 使用引用计数(reference counting) 管理对象实例 这张幻灯片上 用备注 显示出编译器在哪里 插入(insert) 保留(retain) 释放(release)
这是 Swift 4.1 中的运行方式 对象创建时 会关联一个 +1 引用计数 惯例是 如果 对象作为一个参数 传递到另一个函数中 那个函数的调用 就应该释放对象 这基本上就是让 函数调用 释放对象 这样一来 就有 机会改进性能 缩减 一些对象的生命周期 限制到最小范围 但是 如果你的代码 将对象传递到 不同 API 的话 可能会是这样 由于存在这种 调用惯例 就依然存在 这种往复操作 最初的引用计数 在最后的调用中 抵消 但中间的步骤 又应当有额外的 保留和释放过程 因为 这是惯例 这样会造成浪费 因为 对象在函数 执行期间 始终存活(alive) 所以 Swift 4.2 中 我们改变了 调用惯例 这样一来 就不再由被调用的 一方来释放对象 这样一来 所有的保留 和释放都不复存在 可以显著减少 保留和释放的数据量 这有两重意义 一 可以缩减代码体积 因为这些调用不复存在 二 可以改善运行时
另一项重要的优化 针对字符串 Swift 4.2 的字符串为 16 个 字节长 此前长度为 24 这也是我们 权衡内存使用和 性能的结果
但同时 这一长度 也足以对小字符串 进行优化 如果字符串不足 15 字节长 实际的字符串 就可以直接用 字符串类型表示 而不用 分配单独的 缓冲区来表示字符串 这显然既有利于内存 也有利于性能 这很像我们在 NSString 中 所做的优化 但我们可以存储更大的字符串
最后 在我把舞台交给 Slava 之前 我们来谈谈 语言本身的改进 我想多谈一下 缩减代码体积方面的努力
我之前在谈到 调用惯例改变的时候 提到了 缩减代码体积 我们还推出了一个新的 优化水平选项 即优化 体积(Optimize for Size )
这一点很有用 尤其是当 App 非常 关心体积限制的时候 例如 使用蜂窝网络进行 下载的限制
Swift 是一门强大的 静态语言 以静态的方式 理解程序行为 因此 编译器有很多 优化性能的 机会 例如 将函数调用内联(function call inlining) 以及推测性去虚化(speculative devirtualization) 这些方法牺牲代码体积 以换取性能 然而 有时候 这部分性能 并非实际需要 这是对 Swift 源编译套件 实行 Osize 优化的结果 其中 包括一系列 GitHub 项目 如框架和 App 可以看到 优化范围 取决于使用了 哪些语言特性 代码体积 缩减了 10% 到 30% 这里所说的代码体积指的是编译 Swift 代码过程中 构建的机器码 不是 App 的整体体积 App 整体体积取决于 应用素材等多重因素
现实中 我们发现 运行时性能会下降大约 5% 这需要牺牲一小部分性能 对很多 App 而言 这一点 无伤大雅 所以确实取决于实际用例 但如果你对此 感兴趣 我们也鼓励你 进行尝试 现在 我要把舞台 交给 Slava 他会谈到 Swift 4.2 中 语言方面的重大改进 大家好 我是 Slava Pestov 我负责 Swift 编译器 今天 我想谈谈 Swift 4.2 中的新语言特性 可以如何帮助你 写出更简单 更易维护的代码
在讲到语言的 新变化之前 先来 回顾一下改进 语言的过程 如同 Ted 所说 Swift 是一个 开源项目 也具备 开放式设计 也就是说 如果你有 改进语言的想法 你可以在论坛上 提出来 如果想法 得到足够认可 并列入 提议案 你就可以 把想法和 实现提交给核心团队 以供审核 此时 会有正式审核期 社区成员 可以提供额外反馈 之后 核心团队 就会决定是否 接受提议
如果访问 Swift Evolution 网站 就能看到一系列 提议 这些提议均得到了 接受 并将在 Swift 4.2 中实现 列表中包含的提议 数量很多 我今天无法涉及 所有提议 但有一点我想要 强调 那就是 有很多提议是 由社区设计并 实现的 这表明 这些 提议针对 你们遇到的 各类常见问题 也是你们想出了 解决方案 并向 Swift 贡献了 解决方案 使人人收益 谢谢大家
第一项改进 我们来看看 如何在使用枚举时 去除鸡肋代码
比如说 我要获得一个枚举 我想要打印出 这个枚举包含的所有值 在 Swift 4 中 我需要定义 一个属性 例如包括所有 可能样例的列表 如果我要添加新的样例 我要记得更新 属性 否则就会呈现 行为或运行时错误
这种做法并不好 因为你在向编译器 重复自己的工作 所以在 Swift 4.2 中 我们添加了 新的 CaseIterable 协议 如果你表示遵从(conformance) 这一协议 编译器就会 将包含所有样例的属性 同步 简短而有效 下一项改进 我们 要去除另一个 鸡肋代码的根源 这种情况下 你的代码往往 不够泛型(generic) Swift 4 中 我们为序列 引入了 contains() 方法 这就要求 序列的元素类型 为 Equatable 才能找出所需的元素 当然 我可以对 字符串组成的数组调用 因为字符串是 Equatable 类型 但如果对数组组成的数组 调用呢 如果子数组包含整型(integer) 类型就不为 Equatable 也就是说 编译时间错误
你可能会问 为什么 标准库不让 所有数列为 Equatable 呢 但那样不合逻辑 因为如果数列的 元素类型不为 Equatable 例如函数 你也不能让数列为 Equatable
但显然 如果数列的 元素类型为 Equatable 我就可以为数组 定义等值运算 以比较数组中的元素对 而这就是条件一致性 (conditional conformance)对标准库 发挥的作用 现在 如果数组元素 类型为 Equatable 那么数组样例也可以 实现 Equatable Swift 4.2 中 之前展示的 例子可以运行 除了数组为 Equatable 外 标准库还定义了其他一些 条件一致性 例如 选项(options) 和字典(dictionaries)也是 Equatable 前提是它们的 元素类型为 Equatable Hashable Encodable 和 Decodable 同理
这样一来 你就可以 用全新的方法 构建集合(collections) 这里 有一些由 整数构成的数组
一切运行正常 欲知更多细节 本周晚些时候有一场会议 可以进一步了解 条件一致性 以及 Swift 4.2 中 今天无法提及的 泛型改进
如果要定义自己的 Equatable 和 Hashable 成员(conformances)呢 Swift 中的通用做法是 用包含许多存储属性(stored properties)的 结构体来实现 而这些存储属性 应为 Equatable 接下来 要想让 结构体 Equatable 只需比较两个值的 属性即可 此前在 Swift 4 中 你需要 亲手写出这些内容 这就是鸡肋代码 如果添加一个新的 存储属性 就要记得 更新 Equatable 实现 很容易在 拷贝粘贴等过程中出错 Swift 4.1 中 我们可以 合成 equality(相等性) 的使用 如果忘记了实现部分 编译器会为你 补上 前提是 存储属性本身为 Equatable
这也适用于 Hashable
那么如果是泛型呢 这个数据类型中 值可能是 左类型的实例 或者是右类型的实例 我想让左右类型都为 Equatable 因为我想令这两个类型 为函数 错误(errors) 或其他不为 Equatable 的类型 但显然 我可以定义 条件一致性 这样 如果左右类型都为 Equatable 二者之一即为 Equatable 但还可以进一步改进 注意此处的可等性 实现 只有一种 显然正确的实现方式 需要确保两个值 包含相同的样例 如果确实如此 则需要检查载体(payloads) 以确保可等性 你可能会想 编译器应该能够 替你进行合成 Swift 4.2 中确实可以 该功能也可用于 Hashable
比方说 现在就可以创建 元素为整型或字符串的集合了
好的 有时 你确实需要 手动实现 可等性和哈希值(Hashing) 我们来看一个例子
假如说 有一个数据类型 表示一座城市 其中 包括城市名 包括所在的 州 还包括城市的人口 在这个例子中 我只需要 比较城市名和州 以确保可等性 如果数值相等 就不需要 对比人口了 如果我让编译器 合成可等性的 实现 就会产生 冗余 因为它还比较了 人口信息 我当然可以把这部分 写成代码 或许此时 这样还可以 但 Hashable 呢
如果我想计算 城市对象的哈希值 我就要算出 城市名的哈希值和 州的哈希值 再想办法结合二者 但该怎么做呢 可以用异或运算 也可以用 网上随便找的 或者自己编的 运算方式 但这两种办法 都不够好 好像 结合哈希值的函数 是一种魔法似的 而出错的成本 也相当高 因为 字典和集合 要想具备良好性能 确实需要依赖 高质量的哈希函数 同时也有安全考量 如果攻击者 打造的输入能生成 相同的哈希值 并通过互联网发给你的 App 就可能降低 App 运行速度 以致 无法使用 即发起 阻断服务攻击(denial-of-service attack) 所以 在 Swift 4.2 中 我们添加了 更好的 API 实现该功能 可以回想一下 Swift 4 和 4.1 中的 Hashable 协议 其中一个 hashValue 要求(requirement) 返回一个 整型值 Swift 4.2 中 我们重新设计了 Hashable 协议 现在 有了新的 Hash into 要求 Hash into 不产生 单一哈希值 而是选用一个 Hasher 实例 之后可以将 多个值传入 Hasher Hasher 会结合传入值 生成一个哈希值 回到城市数据类型的 例子 我们只需要 对我们给出的 Hasher 对象实例中 传入的城市名和州 进行递归的 Hash into 调用
Hasher 中的 哈希合并算法 可以有效平衡 哈希值质量和性能 还可以作为额外保护 抵御阻断服务攻击 为此 它使用了 App 启动时生成的 随机预处理种子
我们认为 你可以 很轻松地迁移代码 来使用新的 Hashable 协议 我们也鼓励你这样做 有一条警告 你可能原本 希望哈希值 能够在多次运行 App 中 保持不变 或者 在遍历字典 或集合的时候 希望以同一顺序获取元素 这些已经不再可行 因为使用了随机 预处理种子 你可能需要调整代码 为了简化工作 我们 添加了一项构建设置 即 SWIFT_DETERMINISTIC_HASHING 环境变量 你可以 在方案编辑器(Scheme Editor)中激活 以暂时停用 随机预处理种子 接下来 来谈谈 随机数生成
现在 你们在 Swift 中 如何生成随机数 你需要导入 C 语言的 API 这确实不够理想 因为 API 因平台而异 名字不同 行为不同 需要检查构建配置 此外 这些 API 位于底层 还会用到一些 不太直观的常见运算 例如 如果我想得到 1 到 6 之间的随机数 我需要调用 Darwin 中的 arc4random 函数 再除以 6 来求余 但这样的结果 往往并非均匀 分布在 1 到 6 之间 Swift 4.2 中 我们添加了 一组新的 API 以简化操作
首先 我们为所有数字类型 定义了 random 方法 可以获取一个范围 返回 平均分布在范围中的数字 这种方法使用了正确算法 也适用于浮点型(floats)
对于顶层代码 我们 为集合协议添加了 randomElement 方法 可以像 min 和 max 方法一样 返回选项 如果传入空集合 会返回空
最后 我们为集合添加了 shuffle 方法 可以生成一个 该集合元素的随机排列组合的数组 我们认为 默认的随机数 生成器(Random Number Generator) 适合大多数 App 但你也可以采用自己的算法 现在 有了新的 RandomNumberGenerator 协议 一旦你创建一个符合 该协议的类型 就可以将它 传递给我提到的 API API 拥有一个使用 using 参数的 额外重载(additional overload) 可以接收 RandomNumberGenerator 作为输入
好的 之前我们看到了 构建配置检查 我们再多了解一些 这是 Swift 中常见的情况 有一小段代码 在 iOS 和 macOS 间共用 在 iOS 上 你应该 使用 UIKit 在 macOS 上 你应该 使用 AppKit 现在要做到这一点 你要写一段 #if 代码 来进行编译时间检查 之后需要列出 支持 UIKit 的操作系统 但你真正关心的 不是你在运行 什么操作系统 而是导入 UIKit Swift 4.2 中 我们添加了 canImport() 构建配置指令 (Build Configuration Directive) 帮助你表达本意 而利用 Swift 4.2 的新特性 我还可以进一步 完善代码 比方说 我还要 专门检查 AppKit 如果 UIKit 和 AppKit 都不可用 例如我在 Linux 上构建 我就可以使用新的 #error 构建指令来生成 编译时间错误信息
好的 还有一种类似的 鸡肋代码
如果我想在 模拟器环境中 编译代码 在 Swift 4 中 我需要 把这一段东西 拷贝 粘贴到所有我想要进行检查的地方 才能 进行检测 Swift 4.2 中 你可以使用新的 hasTargetEnvironment() 环境判断 来更好地表达本意 还可以直接询问 编译器 我是不是在 为模拟器编译 说到这里 我们再把 FIXME 换成 #warning 构建指令 来生成信息 或运行时间 提醒我别忘了 修复 FIXME 好的 这差不多就是 我今天要讲的 全部特性 但我还有几项要提一下
我们来解析一下 隐式解析选项(Implicitly Unwrapped Optionals) 这个梗没效果啊 好吧 隐式解析选项 可能会有点 让人疑惑 我们先来 看看它的思维模型 应该如何理解它 从 Swift 3 开始 不是表达式的一种类型 不要认为它是一种类型 而要认为 它是声明的 特性之一 当你引用这种声明是 编译器会首先 检查它的类型 看它是不是普通的选项 如果在它的使用场景中 它不合理 编译器就会将其解析 进行类型检查 将其标记为 下划线类型(underlined type)
我们来看看第一种 情况的例子 这里有两个函数 第一个会生成 整型的隐式解析选项 第二个则会接受 任何类型的传入值 我要用第一个函数的 结果 来调用第二个函数 这个例子中 我可以 在任意(Any)类型中存入整型选项 不会进行强制解析 值会变成一个普通的选项 我们再看看第二种情况的例子 现在 第一个函数生成 对不起 第二个函数 现在接受整型 如果我使用第一个函数的 结果 调用第二个 函数 我就不能 传递整型选项 因为 函数需要整型 所以 编译器必须进行 强制解析 之后 代码运行正常 因为这里需要整型 也传入了整型 这个思维模型 可以很好地 帮助理解隐式解析选项 但直到最近 编译器 还是会遇到一些不遵从 此模型的极端案例 所以记住 隐式解析选项 不能是其他类型的一部分 Swift 4.2 中 依然如此 数组不能包含 隐式解析整型 但在 Swift 4 中 有一些像这样的极端案例 我可以定义一个 typealias 它的实际类型可以是 隐式解析整型 接下来 我就可以用这个 typealias 创建数组 从而使编译器的行为 匪夷所思 令代码难以理解 因此 在 Swift 4.2 中 我们 重新采用了 隐式解析选项 完美契合我之前 提到的思维模型 这一段匪夷所思的代码 现在会生成编译时间错误 编译器也会将其 按照普通整型数组 或整型选项数组来解析 大多数代码不会 因隐式解析选项的 这一变化而受影响 但如果你碰巧 用到了这些极端案例 我建议你看一看 Swift.org 上的这篇博客文章 这篇文章包含更多细节 举了很多例子 来说明 变化的种类和方式 好的 还有最后一项 我们来谈谈内存 独占性检查(memory exclusivity checking)
大家还记得 在 Swift 4 中 我们 带来了所谓的 内存独占性检查 该功能结合了 运行时检查 和编译时检查 但一些功能受限 我们专门限制了 对同一内存区域的 重复访问 这意味着什么 我们来看个例子 这一段代码 为操作系统路径 实现了一个数据类型 表现为包含路径元素的 一个数组 还使用了 withAppended 方法
这个方法可以向数组中 添加元素 再调用 closure() 作用于你传入的元素 再将它从数组中移出 这段代码没有问题 在 Swift 4 中运行无误
但我们来看一下 Path 数据类型的使用
这个 path 变量 存储在局部变量中 我对它调用 withAppended 方法 在 closure 中 我再访问 这个局部变量 并将它打印出来 问题出在哪儿 事实上 这段代码 有歧义 因为 当我访问 closure 中的 局部变量时 它已经被 withAppended 这一可变方法(mutating method) 修改过了 歧义之处在于 我所指的是 path 在调用 withAppended 之前的 原始值 还是 修改之后的 当前值 Swift 4 中 这是一处 编译时错误 因为 它违反了独占性 要解决这一问题 消除歧义 办法之一 是告诉编译器 我想要获得当前值 所以 我只将其作为参数 传入 closure 而不去捕获它 可以 但再看看这个例子 这个函数和刚才的 几乎相同 除了这是一个泛型 函数 参数由 closure 返回值的类型决定 这个例子中 我们会遇到 类似的歧义 因为 要从闭包内部获得 path 的值 但 Swift 4 无法在 编译时捕获到这个错误
Swift 4.2 中 我们改进了 静态独占性检查(static exclusivity checking) 可以在更多情况下 指出这样的歧义
除了改进 好吧 你还可以用类似方法 将变量值作为 closure 的参数传入 来消除歧义 除了改进 静态检查 我们还新增了 使用运行时独占性检查 和释放构造的功能 这会牺牲一些性能 但如果你的 App 不注重 性能 我们鼓励你尝试一下 并且始终开启 这一功能 未来 我们会将 动态检查的性能损耗 大幅降低 这样就可以 始终开启这一功能 这一功能还能带来 额外保护 就像 数组边界检查(array bounds checking) 或整型溢出检查(integer overflow checking) 一样 Swift 4.2 中还有 很多我没有涉及的特性 我们鼓励大家 在现有 App 中尝试 我们希望大家多尝试 新特性 如果有 任何问题 欢迎来到 实验室 咨询我们 谢谢
[ 掌声 ]
-