Roadmap.sh Flutter 学习路线图 - 这个交互式路线图将 Flutter 学习内容结构化展示，你可以点击每个技能点查看详细说明，是规划学习进度和查漏补缺的绝佳工具。Flutter 官方文档 - 学习 Flutter 的第一手资料！文档结构清晰，从入门教程到深度指南应有尽有，配有丰富的代码示例和交互式演示。Flutter 中文文档 - 官方文档的中文翻译版。Flutter 官方文档（英文原版）+ 翻译辅助工具 - 社区维护的中文文档会略微落后于官方文档，个人更推荐直接阅读英文官方文档，配合浏览器双语翻译插件，可以对照学习，逐步适应英文技术文档。B站 王叔不秃系列教程 - 用爱发电宝藏 UP，视频由浅入深通俗易懂，5🌟推荐。掘金张风捷特烈 Flutter系列课程 - 3块钱买不了吃亏买不了上当。B站 whoitao Dart 进阶 - 多线程、Stream 进阶资料，PPT 制作精良，力推。CODE WITH ANDREA - Flutter GDE 的专业技术博客，文章质量高、更新及时，是学习 Flutter 最佳实践和高级技巧的优质英文资源。Effective Dartvandadnp / flutter-tips-and-tricksbizz84 / flutter-tips-and-tricks
Flutter 中 State 的生命周期是理解 StatefulWidget 工作原理的关键。以下是 Flutter 中 State 对象的完整生命周期：createState()当框架被要求构建 StatefulWidget 时首先调用必须被覆盖并返回 State 类的实例initState()在 State 对象插入视图树时调用仅被调用一次这里适合进行一次性初始化，如控制器初始化、监听器注册等必须调用 super.initState()didChangeDependencies()在 initState() 之后立即调用当 State 对象的依赖项更改时会再次调用例如，当使用 InheritedWidget (如 Theme 或 MediaQuery) 时，此方法会被调用build()必须实现的方法描述如何构建组件的 UI可能被频繁调用，所以应该是轻量级的每当 widget 需要重新渲染时会调用didUpdateWidget(covariant oldWidget)当父 widget 重建并提供新 widget 时调用widget 的配置发生变化时使用旧的 widget 作为参数传入setState()通知框架 State 对象的内部状态已更改触发 build() 方法重新调用不是生命周期方法，但用于管理状态变化deactivate()当 State 从视图树中移除时调用如果稍后将其重新插入，可能还会再次激活在从一个位置移动到另一个位置的情况下使用dispose()当 State 对象永久从视图树中移除时调用用于清理资源，如取消订阅、销毁控制器等必须调用 super.dispose()这些方法构成了 Flutter 中 State 的完整生命周期。正确理解和使用这些生命周期方法对于创建高效、无内存泄漏的 Flutter 应用非常重要。
类修饰符可以用在 class 关键词前面，例如 abstract class 用于定义一个抽象类。类修饰符种类：abstractbasefinalinterfacesealedmixin除 abstract 外，其他修饰符均需要 dart version => 3.0 。仅有 base 可以出现在 mixin 前面。修饰符不能其他诸如 enum, typedef, extension 或 extension type 的声明中使用。无修饰符对于无修饰符的 class ,允许不受限制地从任何库构造或子类型. 允许以下操作：实例化;扩展一个子类;实现一个类或者一个mixin;mixin 一个 mixin 或者 mixin class.abstract对于不需要完整实现的,只需要定义接口的场景使用 abstract，abstract class 不允许实例化.// 声明一个 abstract class abstract class Vehicle { void moveForward(int meters); } import 'a.dart'; // 错误: abstract class 不允许实例化 Vehicle myVehicle = Vehicle(); // ✅被扩展 class Car extends Vehicle { int passengers = 4; // ··· } // ✅被实现 class MockVehicle implements Vehicle { @override void moveForward(int meters) { // ... } }base允许继承，不允许实现。// 声明一个 base class base class Vehicle { void moveForward(int meters) { // ... } } import 'a.dart'; // ✅允许实例化 Vehicle myVehicle = Vehicle(); // ✅允许扩展 base class Car extends Vehicle { int passengers = 4; // ... } // ❌不允许实现 base class MockVehicle implements Vehicle { @override void moveForward() { // ... } } interface可以被实现，但是不允许扩展。interface class Vehicle { void moveForward(int meters) { // ... } } import 'a.dart'; // ✅允许实例化 Vehicle myVehicle = Vehicle(); // ❌不允许继承 class Car extends Vehicle { int passengers = 4; // ... } // ✅允许被实现 class MockVehicle implements Vehicle { @override void moveForward(int meters) { // ... } } abstract 和 interface 可以组合使用，abstract interface class。final关闭类的结构层次，禁止被扩展和实现。final class Vehicle { void moveForward(int meters) { // ... } } // ✅允许实例化 Vehicle myVehicle = Vehicle(); // ❌不允许被扩展 class Car extends Vehicle { int passengers = 4; // ... } // ❌不允许被实现 class MockVehicle implements Vehicle { // ERROR: Can't be implemented. @override void moveForward(int meters) { // ... } }sealed用于创建一个可以被穷举的子类合集。声明一个 sealed class sealed class Vehicle {} class Car extends Vehicle {} class Truck implements Vehicle {} class Bicycle extends Vehicle {} // ❌不允许实例化 Vehicle myVehicle = Vehicle(); // ✅子类允许实例化 Vehicle myCar = Car(); String getVehicleSound(Vehicle vehicle) { //❌编译错误，switch 缺少子类 Bicycle case，或者 default case. return switch (vehicle) { Car() => 'vroom', Truck() => 'VROOOOMM', }; } 组合使用类修复符允许按如下顺序组合使用：（可选）abstract，描述类是否可以包含抽象成员并阻止实例化。（可选）base、interface、final 、sealed中的一个，描述对该类进行子类型化的其他库的限制。（可选）mixin，描述是否可以 mixin。关键字class本身。不能组合某些修饰符，因为它们是矛盾的、冗余的或相互排斥的：abstract 和 sealed。sealed 类是隐式抽象的。interface, final 、sealed 与 mixin。这些修饰符不允许 mixin.使用场景分析//TODO
原文：Xcode Build Configuration Files软件开发最佳实践 规定了 配置与代码的严格分离。然而，Apple 平台上的开发人员通常很难将这些准则与 Xcode 的项目繁琐的工作流程相结合。了解每个项目设置的功能以及它们之间如何交互，是一项需要多年磨练的技能。然而很多信息都被深藏在Xcode的GUI中，事实上这对我们没有帮助。导航到项目编辑器的 “Build Settings” tab，你会看到分布在 project、target 和 configuration 上的 数百条 Build Setting —— 更不用说其他六个 tab 了!幸运的是，有一个更好的办法，不必点击迷宫般的 tab 和箭头，就可以管理所有的配置。本周，我们将向您展示如何使用基于文本的 xcconfig文件 从 Xcode 外部化构建设置，以使您的项目更加紧凑，易懂且功能强大。请访问XcodeBuildSettings.com以获取最新版本的Xcode支持的每个构建设置的完整参考。Xcode Build 配置文件 他们的 xcconfig 文件扩展名更加众所周知。允许在不使用 Xcode 的情况下声明和管理应用程序的构建设置。它们是纯文本格式，这意味着它们对代码管理系统更加友好，并且可以使用任何编辑器进行修改。从根本上讲，每个配置文件都由一系列键值分配组成，这些键值分配具有以下语法：BUILD_SETTING_NAME = value例如，你可以使用下面这样的 SWIFT_VERSION Build Setting，指定项目的 Swift 语言版本：SWIFT_VERSION = 5.0根据posix 标准，环境变量的名称仅由大写字母，数字和下划线（_）组成，这是一个惯例，我喜欢称之为SCREAMING_SNAKE_CASE ??.乍一看，xcconfig 文件与.env 文件具有惊人的相似之处，它们具有简单的换行符分隔的语法。但是，Xcode构建配置文件不仅仅让人眼前一亮。看哪！保留现有值要附加而不是替换现有定义，请使用$(inherited)变量，如下所示：BUILD_SETTING_NAME = $(inherited) additional value通常，您这样做是为了建立值列表，例如编译器在其中搜索框架以查找包含的头文件（FRAMEWORK_SEARCH_PATHS）的路径：FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)Xcode按以下顺序分配继承的值（从最低优先级到最高优先级）：Platform Defaults(平台默认值)Xcode Project File Build Settings(Xcode 项目文件的 Build Setting)xcconfig File for the Xcode Project(Xcode 项目的 xcconfig 文件)Active Target Build Settings(Active Target 的 Build Setting)xcconfig File for the Active Target(Active Target 的 xcconfig 文件)空格用于分隔字符串和路径列表中的项。指定包含空格的项时，必须用引号(")括起来。引用其他值你可以按照下面的语法，通过其他设置的名字引用它们的值：BUILD_SETTING_NAME = $(ANOTHER_BUILD_SETTING_NAME)这种引用既可以用于根据现有值定义新变量，也可以用于以内联方式动态构建新值。OBJROOT = $(SYMROOT) CONFIGURATION_BUILD_DIR = $(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)设置参考的构建设置的默认值在Xcode 11.4和更高版本中，可以使用默认的评估运算符来指定回退值，如果引用的构建设置的评估结果为空，则使用该替代值。$(BUILD_SETTING_NAME:default=value)条件编译设置您可以根据以下语法根据其SDK（sdk），architecture（arch）和/或configuration（config）来对 Build Setting 进行条件约束：BUILD_SETTING_NAME[sdk=sdk] = value for specified sdk BUILD_SETTING_NAME[arch=architecture] = value for specified architecture BUILD_SETTING_NAME[config=configuration] = value for specified configuration如果需要在同一 Build Setting 的多个定义之间进行选择，编译器将根据条件约束进行解析。BUILD_SETTING_NAME[sdk=sdk][arch=architecture] = value for specified sdk and architectures BUILD_SETTING_NAME[sdk=*][arch=architecture] = value for all other sdks with specified architecture例如，你可以使用下面这条 Build Setting 指定仅编译 active architecture，从而提升本地 Build 的速度。ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES引入来自其他配置文件的构建设置跟 C 语言的 #include 指令一样，Build 配置文件也可以使用这种语法来引用其他配置文件中的设置。#include "path/to/File.xcconfig"正如我们将在本文后面看到的那样，您可以利用此功能以非常强大的方式构建构建设置的级联列表。正常来说，当遇到一个无法解析的 #include 指令时，编译器会报错。但是 xcconfig 文件同时也支持 #include? 指令，在该指令下，若文件无法找到，编译器不会报错。根据文件是否存在而改变编译时行为的情况并不多；毕竟，Build 最好是可预见的。但是你可以把它用在可选的开发工具上，比如 Reveal 需要以下的配置：# Reveal.xcconfig OTHER_LDFLAGS = $(inherited) -weak_framework RevealServer FRAMEWORK_SEARCH_PATHS = $(inherited) /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries创建 Build 配置文件要创建 Build 配置文件，请选择 “File > New File…” 菜单项（⌘N），下拉到 “Other” 部分，选中 Configuration Settings File 模板。将它保存到你的项目目录，并确保它在你期望的 target 上。创建好 xcconfig 文件后，你就可以将它分配给对应 target 的一个或多个 Build 配置。现在我们已经介绍了 Xcode Build 配置文件使用的基础知识，那么让我们来看几个示例，看看如何使用它们来管理 development、stage 和 production 环境。为内部版本提供自定义的 APP 名称和图标开发 iOS 应用程序时，通常需要在模拟器和测试设备上安装各种内部版本（同时也会安装应用程序商店的最新版本，以供参考）。使用 xcconfig 文件，你可以轻松地为每个配置分配一个不同的名称和 APP 图标。// Development.xcconfig PRODUCT_NAME = $(inherited) α ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Alpha --- // Staging.xcconfig PRODUCT_NAME = $(inherited) β ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Beta管理不同环境下的常量如果你的后端开发人员也遵循前面提到的 12 Factor App 理论，那么他们将为 development、stage 和 production 环境提供单独的接口。iOS 上最常见的环境管理方式可能就是使用条件编译语句 + DEBUG 这样的 Build Setting 了。import Foundation #if DEBUG let apiBaseURL = URL(string: "https://api.staging.example.com")! #else let apiBaseURL = URL(string: "https://api.example.com")! #endif这只是完成了任务，但是与代码 / 配置分离的标准相冲突。另一个方案是将这些与环境相关的值放到它们该待的地方 —— xcconfig 文件中。// Development.xcconfig API_BASE_URL = api.example.dev API_KEY = 9e053b0285394378cf3259ed86cd7504 --- // Production.xcconfig API_BASE_URL = api.example.com API_KEY = 4571047960318d233d64028363dfa771不幸的是，xcconfig 将所有 // 都当成注释分隔符，不管它们是否包括在引号中。如果你用反斜线 \/\/ 进行转义，那么这些反斜线也将被直接展示出现，使用时必须从结果中移除。在指定每个环境的 URL 常量时，这尤其不方便。如果不想处理这种麻烦的事情，你可以在 xcconfig 中忽略 scheme，然后在代码中添加 https://。_（你是在使用 https……对吧？）_然而，要以编程方式获取这些值，我们还需要一个额外的步骤:在 Swift 中访问 Build Setting由 Xcode 项目文件、xcconfig 文件和环境变量定义的 Build Setting 只在 Build 时可用。当你运行一个已经编译的 APP 时，所有相关的上下文都是不可见的。_（谢天谢地！）_但是等一下——你不记得之前在其他 tab 中看到过一些 Build Setting 吗？Info，是吗？实际上，Info tab 只是 target 的 Info.plist 文件的一个马甲。Build 时，这个 Info.plist 文件会根据 Build Setting 的配置进行编译，然后复制到最终 APP 的 bundle 中。因此，添加 $(API_BASE_URL) 和 $(API_KEY) 的引用后，你可以通过 Foundation Bundle API 的 infoDictionary 属性访问这些值。完美！按照这种方法，我们可以做如下工作：import Foundation enum Configuration { enum Error: Swift.Error { case missingKey, invalidValue } static func value<T>(for key: String) throws -> T where T: LosslessStringConvertible { guard let object = Bundle.main.object(forInfoDictionaryKey:key) else { throw Error.missingKey } switch object { case let value as T: return value case let string as String: guard let value = T(string) else { fallthrough } return value default: throw Error.invalidValue } } } enum API { static var baseURL: URL { return try! URL(string: "https://" + Configuration.value(for: "API_BASE_URL"))! } }从调用的角度考虑，我们发现这种方法与我们的最佳实践完美地在结合一起 —— 没有出现一个硬编码的常量!let url = URL(string: path, relativeTo: API.baseURL)! var request = URLRequest(url: url) request.httpMethod = method请勿使用xcconfig文件存储API密钥或其他凭据之类的机密。有关更多信息，请参阅我们的文章secret management on iOS。Xcode 项目是庞大、脆弱的和不透明的。它们是团队成员之间进行协作的摩擦之源，并且通常是工作上的累赘。幸运的是，xcconfig文件可以解决这些难题。将配置移出Xcode并移入xcconfig文件可带来许多好处，并提供一种使项目与Xcode的细节保持距离的方法，而无需离开Cupertino(注：Apple 的总部在Cupertino)认可的“幸福道路”。
前言苹果已经从iTunes的12.7版本中删除了App Store，这使得从App Store下载IPA变得更加困难。但是，现在可以通过使用Apple Configurator 2来实现。Apple Configurator 2选中设备，点击 Add -> Apps如果设备上之前没有当前添加的 App，会直接安装。如果该 App 已经再设备上存在，会询问是否覆盖，此时不要做选择。打开 使用 iTerm 或者 PopClip打开进去如下路径：~/Library/Group Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps/应该看到带有随机生成的ID的目录，深入到该目录应该就能找到IPA文件了。
原文: A successful Git branching modelWhy git?有关Git与集中式源代码控制系统相比的利弊的详细讨论，请参阅网站。那里战火纷飞。作为开发人员，我喜欢Git超过所有的其他工具。 Git 真的改变了开发人员对合并和分支的看法。在 古老的的 CVS / Subversion 世界里，合并/分支一直被认为有点可怕（“小心合并冲突，它们会咬你”），但是你每隔一段时间都要做。但是使用Git，这些操作非常容易和简单，并且它们被认为是你日常工作流程的核心部分之一。例如，在 CVS / Subversion 书籍中，分支和合并通常在最后的章节才第一次谈论（对于高级用户），而在每本Git书中，它已经在第3章（基础知识）中介绍过了。由于其简单性和重复性，分支和合并不再是一件令人害怕的事情。版本控制工具应该比其他任何东西更有助于分支/合并。准备好工具，让我们进入开发模型。我将在这里介绍的模型基本上只是每个团队成员必须遵循的一组步骤以便来管理软件开发过程。Decentralized but centralized我们使用的仓库设置与该分支模型配合良好，具有一个“真实”中央仓库。请注意，这个仓库只被认为是中央仓库（因为Git是DVCS，在技术层面没有中央仓库）。我们将此repo称为origin，所有Git用户应该都熟悉此名称。每个开发人员都会pull并puch 到 origin。但除了集中式 push-pull 关系之外，每个开发人员还可以从其他同行中获取更改以形成子团队。例如，在将正在进行的工作过早地推送到 origin 中之前，这对于与两个或更多开发人员一起处理大型新功能可能是有用的。在上图中，有 Alice 和 Bob，Alice 和 David 以及 Clair和David 的子团队。从技术上讲，这意味着Alice已经定义了一个名为 bob 的Git remote，指向Bob的仓库，反之亦然。The main branches事实上，这个开发模型受到现有模型的极大启发。中央仓库拥有两个主要分支，具有无限的生命周期：masterdeveloporigin 上的 master 分支每个Git用户应该都熟悉。与 master(本地) 分支并行，另一个分支称为develop。我们认为 origin/master 是主分支，其中HEAD的源代码总是反映生产就绪状态。我们将 origin/develop 视为主要分支，而它的 HEAD 指针始终指向为下一发版而做的最新开发改动。有些人称之为“整合分支”(integration branch)。这是建立任何自动夜间构建的地方。当 develop 分支中的源代码达到稳定点并准备好发布时，所有更改都应以某种方式合并回master，然后使用版本号进行标记。具体如何做我们将进一步讨论。因此，每次将更改合并回master时，根据定义，这是一个新的生产版本。我们对此非常严格，因此从理论上讲，我们可以使用Git钩子脚本在每次有master提交时自动构建和推出我们的软件到我们的生产服务器。Supporting branches主干分支master和develop之外，我们的开发模型使用了很多辅助分支来帮助团队成员间的并行开发，减轻功能跟踪的成本，预备生产环境的发布，协助快速修复已发布版本的问题。和主干分支相反，这些辅助分支只有有限的生命期，最终会被移除。我们可能会用到的辅助分支有：Feature branchesRelease branchesHotfix branches上面的每个辅助分支都有一个特定的目标，且严格限制哪些是起源分支，哪些是合并的目标分支。我们一会儿会详细解释。从技术角度来看，这些分支绝不是”特殊的”。分支的类型是按照我们如何使用它们而划分的，也就是普通的 Git 分支而已。Feature branches可以从 develop 派生。必须合并到 develop 分支。分支命名公约： 除 master 、develop、 release-_ 和 hotfix-_ 以外的任意命名。Feature 分支（有时称为 topic 分支），是用来为未来的发版而开发新 features 的分支。当开始 feature 的开发时，。Feature 分支的本质它会在 feature 的开发期内存在，但最终会合并回 develop 分支（一定会在即将发布的版本中添加新功能），或者被直接丢弃掉（在令人失望的试验的情况下）。Feature 分支通常仅存在于开发人员的 repos 中，而不在 origin 中。Creating a release branch要创建一个新的 Feature 分支，从 develop 分支上派生：$git checkout -b myfeature develop Switched to a new branch "myfeature"Incorporating a finished feature on develop完成的功能可以合并到develop分支中，以确保将它们添加到即将发布的版本中：$ git checkout develop Switched to branch 'develop' $ git merge --no-ff myfeature Updating ea1b82a..05e9557 (Summary of changes) $ git branch -d myfeature Deleted branch myfeature (was 05e9557). $ git push origin develop --no-ff 标识使得 merge 操作总是创建一个新的 commit，即使 merge 可以使用 fast-forward。这避免了 feature 分支历史信息的丢失，而且把所有 commit 归并在一起。比较一下：后者无法从 Git 历史中找到是哪些 commit 实现了 feature，你必须要手工的去阅读所有的 log 信息。撤销一个 feature 在后一种情况下真的很头疼，如果使用了--no-ff标志，很容易做到。是的，它会创建一些（空的）提交对象，但增益远远大于成本。Release branches可以 develop 分支派生。必须合并回 develop 和 master 分支。分支命名公约: release-*Release 分支为新的生产版本做预备。最后的画龙点睛。此外，Release 分支也可以用来修复小 bug 和准备发布版本的元数据（版本号，构建日期等）。通过在 Release 分支上完成所有这些工作，develop 分支将清晰明了，以便接收下一个大版本的发布。从 develop 分支新发布分支的关键时刻是开发（几乎）反映新版本的期望状态。。至少所有针对要构建的版本的功能在此时必须已经合并进 develop。针对未来版本的所有功能可能不会 – 他们必须等到release分支派生后。确切的说，release 分支始于即将发布的一个版本分配了一个版本号–不能提早。直到那一刻之前，develop 分支反映出“next release” 的改动。但还不清楚“下一次发布”最终是否会变成0.3或1.0，对 release 版本的决定是在 release 分支开始时，由项目的版本号规则来拟定。Creating a release branchRelease 分支是从develop分支创建的。例如，当前的生产版本是 1.15，且即将有一个大迭代。develop 分支的状态已经为 next release 做好准备，我们也已经决定好即将到来的版本是 1.2（而不是 1.1.6或者2.0）。因此我们从当前的 develop 分支上派生并给他起一个反应了新版本号的名字：$ git checkout -b release-1.2 develop Switched to a new branch "release-1.2" $ ./bump-version.sh 1.2 Files modified successfully, version bumped to 1.2. $ git commit -a -m "Bumped version number to 1.2" [release-1.2 74d9424] Bumped version number to 1.2 1 files changed, 1 insertions(+), 1 deletions(-) 创建并切换到新分支后，我们遇到了版本号。在这里，bump-version.sh 是一个虚拟出来的 shell 脚本，用来修改一些文件，使其能体现新版本。之后，遇到的版本号被 commit 上去。该新分支可能会存在一段时间，直到 release 可以确定被发布出来。在此期间，分支上可能会修复一些 bug（而不是在 develop 分支上）。严禁在该分支上添加大的 feature 进来。release 分支最后必须被 merge 回 develop 分支上，然后继续等待下一个大版本的发布。Finishing a release branch当 release 分支的开发状态到了真正可以发布的时候，就需要采取一些行动了。首先， release 分支得 merge 到 master 分支（记住，master 上的每个 commit 都是一个定义的新的 release）。然后，提交到 master 上的 commit 必须被标记上 Tag，以便将来参考该历史版本。最后，release 分支上的改动还需要 merge 回 develop 分支，以便以后的 release 也包含这些 bug fixes。两步 Git 操作:$ git checkout master Switched to branch 'master' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes) $ git tag -a 1.2 现在，release 已经完成，并且标记了 Tag。（你可能还想使用 -s 或 -u 来对 Tag 进行加密）为了保留 release 分支上的改动，继续将他们 merge 回 develop 分支，Git 操作如下：$ git checkout develop Switched to branch 'develop' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes) 这些操作可能会带来代码冲突（很可能，自我们改变了版本号以来），尝试解决冲突后再提交。 现在，我们已经彻底完成工作并且 release 分支已经可以被移除了：$ git branch -d release-1.2 Deleted branch release-1.2 (was ff452fe). Hotfix branches从 master 分支派生； 必须 merge 回 develop 分支和 master 分支； 分支命名公约：hotfix-*Hotfix 分支和 release 分支很类似，都是为发布新的生产版本而准备，虽然是计划外的。其存在的必要性是由于当前生产版本处于需要紧急修复的状态。当生产版本发生严重 bug 必须立即修复时，hotfix 分支可以从生产版本的 master 分支上的相应 Tag 派生出来。这样做的本质是在 develop 分支上开发的人员可以继续，另一边，其他人员则可以快速的进行生产版本的修复工作。Creating the hotfix branchHotfix 分支从 master 派生。比如，当前线上运行的生产版本是 1.2 版，遇到严重 bug，但是 develop 分支上的版本还不到稳定版，就可以派生 hotfix 分支然后开始修复问题：$ git checkout -b hotfix-1.2.1 master Switched to a new branch "hotfix-1.2.1" $ ./bump-version.sh 1.2.1 Files modified successfully, version bumped to 1.2.1. $ git commit -a -m "Bumped version number to 1.2.1" [hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1 1 files changed, 1 insertions(+), 1 deletions(-) 别忘了在派生分支后的版本号。然后，一个或者分多次修复 bug 并提交上去：$ git commit -m "Fixed severe production problem" [hotfix-1.2.1 abbe5d6] Fixed severe production problem 5 files changed, 32 insertions(+), 17 deletions(-) Finishing a hotfix branch完成后，bug修复需要合并到 master 分支上，同时还得 合并到 develop 分支，以保证 bugfix 也包含在下一个版本中 ，这和 release 分支的结束过程是非常类似的。首先，更新master ，然后为新的 release 标记 Tag：$ git checkout master Switched to branch 'master' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes) $ git tag -a 1.2.1 （你可能还想使用 -s 或 -u 来对 Tag 进行加密）然后， develop 分支也包含这些 bugfixs：$ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes) 有一个例外的原则是，如果当前有一个 release 分支存在，那么 hotfix 分支上的改动需要合并到 release 分支，而不是 develop 分支。当 release 分支结束时，合并到 release 分支上的修复代码最终还是会合并到 develop 上。最后，移除这个临时分支：$ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6). Summary虽然这个分支模型并没有什么令人耳目一新的新功能，但这篇文章开头的“大图”在我们的项目中非常有用。它构造了易于理解并允许团队成员开发公共分支和发布流程的优雅模型。
原文链接：What’s New in Swift 5?Swift 5 最终伴随着Xcode 10.2一起到来。这次发布带来了 ABI 稳定性 并且改进了语言，一些期待已久的特性。来看看这些新东西吧！在这篇教程，你将学习Swift 5的一些重大改变。使用 Swift 5 需要Xcode 10.2，所以在开始前请确认你安装了它。Getting StartedSwift 5 源兼容(source compatible) Swift 4.2，但是它不二进制兼容（binary compatible）更早的Swift 发行版。不过由于 ABI 稳定性，未来发行版将与Swift 5二进制兼容。ABI 稳定性让使用不同 Swift版本编译的应用程序和库之间的二进制兼容性。Swift 标准库和运行时嵌入在 OS 中，因此应用程序不会在任何平台上分发自己的库副本。这导致更好的 工具解耦(tool decoupling) 和 OS 集成(OS integration)。你还需要 ABI 稳定性来分发跨多个 Swift 版本的二进制frameworks(binary frameworks )。这需要模块格式稳定性(module format stability)，稳定模块包含编译器的框架公开接口表示的模块文件。你可以在本教程的每个章节找到 Swift Evolution像 [SE-0001] 这样的提议号。你可以浏览每个提案的链接来详细了解每项新变化。最好是在 playground 中尝试这些新特性。启动 Xcode 10.2，选择 File ▸ New ▸ Playground，在 platform 项选择 iOS ，template 选 Blank。命名和保存位置随意，你开心就好。开始吧！需要快速复习一下 Swift 4.2 重点，查看 Swift 4.2教程。What’s New in Swift 4.2?Language ImprovementsSwift 5有很多语言特性，如 动态可调用类型(dynamic callable types),处理未来的枚举(handling future enumeration)等等.Testing Integer Multiples在Swift 4.2中，你可以使用余数运算符确定数字是否是另一个的倍数：let firstNumber = 4 let secondNumber = 2 if secondNumber != 0 && firstNumber % secondNumber == 0 { print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)") }这段代码如何工作：确认 secondNumber 不为 0.确认 firstNumber除以secondNumber余 0.执行除法操作。你必须确认 secondNumber 不为 0,否则 % 操作将抛出错误。Swift 5 通过给BinaryInteger添加了 isMultiple(of:) 方法简化了这一操作SE-0225:if firstNumber.isMultiple(of: secondNumber) { print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)") }即使你传入 0 作为参数,isMultiple(of:)依然可以工作，而且是代码更加简洁明了。Escaping Raw StringsSwift 4.2使用转义符来表示字符串中的反斜杠和引号：let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2." let multiline = """ You use escape sequences for \"\"\"quotes\"\"\"\\\"\"\"backslashes\"\"\" on multiple lines in Swift 4.2. """Swift 5添加了 raw strings 。你可以在字符串的开头和结尾添加＃，这样你就可以使用反斜杠和引号而不会出现问题:let raw = #"You can create "raw"\"plain" strings in Swift 5."# let multiline = #""" You can create """raw"""\"""plain""" strings on multiple lines in Swift 5. """#在原始字符串中使用字符串插值时，必须在反斜杠后使用 # 号：let track = "Nothing Else Matters" print(#"My favorite tune\song is \#(track)."#)在某些情况下，你需要在字符串的开头和结尾使用多个＃：let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."## 在上面的代码中，你在开头添加##在 hashtag结尾添加 #，这样可以在字符串中表示＃。字符串开头使用的 # 数量必须与其末尾的数量相同。在Swift 4.2中，你在正则表达式中转义反斜杠，如下所示：// 1 let versions = "3 3.1 4 4.1 4.2 5" let range = NSRange(versions.startIndex..., in: versions) // 2 let regex = try! NSRegularExpression(pattern: "\\d\\.\\d") // 3 let minorVersions = regex.matches(in: versions, range: range) // 4 minorVersions.forEach { print(versions[Range($0.range, in: versions)!]) }声明 versions定义一个 range 覆盖整个字符串定义一个匹配 versions 中所有次要Swift发行版(minor Swift releases)的正则表达式。使用matches(in:options:range:)确定次要发行版的rangs 。使用 rangs 从 versions中获得次要发行版。Swift 5使用 raw strings 简化正则表达式:let regex = try! NSRegularExpression(pattern: #"\d\.\d"#)在上述代码中，你定义 regex用的反斜杠少了一半，因为你不需要在 raw strings 中转义反斜杠。需要了解更多的关于正则表达式如何在Swift中使用，请看这篇教程: An Introduction to Regular ExpressionsUsing New Character Properties在处理characters时，Swift 4.2 需要一个解决方法来处理一个常见的需求：let id = "ID10" var digits = 0 id.forEach { digits += Int(String($0)) != nil ? 1 : 0 } print("Id has \(digits) digits.") 在此代码中，首先将每个字符转换为String，然后再转换为Int，以确定 id 有多少数字。但是，Swift 5为Character添加了属性，使 characters 更易用SE-0221: 1 id.forEach { digits += $0.isNumber ? 1 : 0 } 在上述情况中，你使用 isNumber 来检查每个字符是否都是数字。查看提案来寻找其他你能使用的属性。Using New Unicode Scalar Properties在 Swift4.2 中，你为Unicode scalars 实现文本处理算法，如下所示：let username = "bond007" var letters = 0 username.unicodeScalars.forEach { letters += (65...90) ~= $0.value || (97...122) ~= $0.value ? 1 : 0 } print("Username has \(letters) letters.")在此代码中，您通过检查每个字符的 unicode scalars是代表小写字母还是大写字母来计算用户名的字母数。Swift 5为 unicode scalars 添加了属性，简化了文本处理SE-0211:username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 } 在此代码中，你使用isAlphabetic 检查每个字符是否为数字。链接的提案将显示你可以检查的所有属性。Removing SubsequencesSwift 4.2 使用下列方法 从 Sequence customization points返回 SubSequence:extension Sequence { func remove(_ s: String) -> SubSequence { guard let n = Int(s) else { return dropLast() } return dropLast(n) } } let sequence = [5, 2, 7, 4] sequence.remove("2") // [5, 2] sequence.remove("two") // [5, 2, 7] 上述情况中，如果 s 是一个 Int那么 remove(_:)将从 sequence移除倒数的 n 个元素,否则移除最后一个元素。Swift 5用 sequences 中的具体类型替换SubSequence。extension Sequence { func remove(_ s: String) -> [Element] { guard let n = Int(s) else { return dropLast() } return dropLast(n) } }在这段代码中，remove(_:) 跟随 dropLast() 和 dropLast(_:) 一样返回 [Element]类型。Dictionary UpdatesSwift 5为 Dictionary 带来了许多期待已久的改进:Compacting DictionariesSwift 4.2使用 mapValues,filter和 reduce来从过滤 Dictionary 中的 nil 值：let students = ["Oana": "10", "Nori": "ten"] let filterStudents = students.mapValues(Int.init) .filter { $0.value != nil } .mapValues { $0! } let reduceStudents = students.reduce(into: [:]) { $0[$1.key] = Int($1.value) }上述代码使用 mapValues加上mapValues或者 reduce 来认定 students中成绩有效的学生。 两种方法都需要多次通过 dictionary而且使代码复杂化。Swift 5 使用 compactMapValues(_:)来高效解决SE-0218(https://github.com/apple/swift-evolution/blob/master/proposals/0218-introduce-compact-map-values.md):let mapStudents = students.compactMapValues(Int.init)使用更少的代码行数完成了相同的事情，整洁！Renaming Dictionary LiteralsSwift 4.2使用 DictionaryLiteral来来声明一个Dictionary: let pets: DictionaryLiteral = ["dog": "Sclip", "cat": "Peti"] DictionaryLiteral 不是一个 Dictionary 也不是 literal. 它是一个键值对表。Swift 5 重命名 DictionaryLiteral 为 KeyValuePairsSE-0214(https://github.com/apple/swift-evolution/blob/master/proposals/0214-DictionaryLiteral.md):let pets: KeyValuePairs = ["dog": "Sclip", "cat": "Peti"] Numeric Protocol UpdatesSwift 4.2为向量实现了 Numeric:// 1 struct Vector { let x, y: Int init(_ x: Int, _ y: Int) { self.x = x self.y = y } } // 2 extension Vector: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) { x = value y = value } } // 3 extension Vector: Numeric { var magnitude: Int { return Int(sqrt(Double(x >md.png COPYING Config.plist CopyAsMarkdown-demo.mp4 README.md _Signature.plist html2md.sh html2text.py x + y >md.png COPYING Config.plist CopyAsMarkdown-demo.mp4 README.md _Signature.plist html2md.sh html2text.py y))) } init?<T>(exactly value: T) { x = value as! Int y = value as! Int } static func +(lhs: Vector, rhs: Vector) -> Vector { return Vector(lhs.x + rhs.x, lhs.y + rhs.y) } static func +=(lhs: inout Vector, rhs: Vector) { lhs = lhs + rhs } static func -(lhs: Vector, rhs: Vector) -> Vector { return Vector(lhs.x - rhs.x, lhs.y - rhs.y) } static func -=(lhs: inout Vector, rhs: Vector) { lhs = lhs - rhs } static func *(lhs: Vector, rhs: Vector) -> Vector { return Vector(lhs.x >md.png COPYING Config.plist CopyAsMarkdown-demo.mp4 README.md _Signature.plist html2md.sh html2text.py rhs.y, lhs.y >md.png COPYING Config.plist CopyAsMarkdown-demo.mp4 README.md _Signature.plist html2md.sh html2text.py rhs.x) } static func *=(lhs: inout Vector, rhs: Vector) { lhs = lhs >md.png COPYING Config.plist CopyAsMarkdown-demo.mp4 README.md _Signature.plist html2md.sh html2text.py rhs } } // 4 extension Vector: CustomStringConvertible { var description: String { return "(\(x) \(y))" } }这段代码如何工作：为 vector 声明x，y和 init(_:_:)。实现 init(integerLiteral:)方法来使 Vector 遵守 Numeric必须遵守的 ExpressibleByIntegerLiteral。定义 vector 的magnitude使其符合 Numeric,声明 init(exactly:)然后实现 +(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:), *(lhs:rhs:), *=(lhs:rhs:)。实现 description 使 Vector 遵守 CustomStringConvertible。上面的代码使你可以轻松地使用 vector :var first = Vector(1, 2) // (1,2) let second = Vector(3, 4) // (3,4) let third = first + second // (4,6) first += second // (4,6) let fourth = first - second // (1,2) first -= second // (1,2) Swift 5为 vector 实现 AdditiveArithmetic，因为你无法定义2D向量的叉积。SE-0233(https://github.com/apple/swift-evolution/blob/master/proposals/0233-additive-arithmetic-protocol.md)。它在 ExpressibleByIntegerLiteral 中不是必须的:extension Vector: AdditiveArithmetic { static var zero: Vector { return Vector(0, 0) } static func +(lhs: Vector, rhs: Vector) -> Vector { return Vector(lhs.x + rhs.x, lhs.y + rhs.y) } static func +=(lhs: inout Vector, rhs: Vector) { lhs = lhs + rhs } static func -(lhs: Vector, rhs: Vector) -> Vector { return Vector(lhs.x - rhs.x, lhs.y - rhs.y) } static func -=(lhs: inout Vector, rhs: Vector) { lhs = lhs - rhs } }在这段代码，你通过定义 zero和实现 +(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:)。想学习更多关于Swift中的操作符重载，访问操作符重载的教程Overloading Custom Operators in SwiftString Interpolation UpdatesSwift 4.2 通过 interpolating segments 实现 字符串的插入:let language = "Swift" let languageSegment = String(stringInterpolationSegment: language) let space = " " let spaceSegment = String(stringInterpolationSegment: space) let version = 4.2 let versionSegment = String(stringInterpolationSegment: version) let string = String(stringInterpolation: languageSegment, spaceSegment, versionSegment)在上述代码，首先包裹每个部分加入init(stringInterpolationSegment:)，然后把所有的部分用 init(stringInterpolation:)包起来。Swift 5带来了完全不同的处理方法SE-0228(https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md):// 1 var interpolation = DefaultStringInterpolation( literalCapacity: 7, interpolationCount: 1) // 2 let language = "Swift" interpolation.appendLiteral(language) let space = " " interpolation.appendLiteral(space) let version = 5 interpolation.appendInterpolation(version) // 3 let string = String(stringInterpolation: interpolation) 这段代码做了什么：用指定容量和插入计数定义了一个 DefaultStringInterpolation实例。调用 appendLiteral(_:)和 appendInterpolation(_:)来添加文字和插入数值到interpolation。通过调用 init(stringInterpolation:)生成最终的插值字符串。Handling Future Enumeration CasesSwift 4.2 不能恰当的处理新的枚举值，看下面的代码：// 1 enum Post { case tutorial, article, screencast, course } // 2 func readPost(_ post: Post) -> String { switch post { case .tutorial: return "You are reading a tutorial." case .article: return "You are reading an article." default: return "You are watching a video." } } // 3 let screencast = Post.screencast readPost(screencast) // "You are watching a video." let course = Post.course readPost(course) // "You are watching a video." 上面的代码发生了什么：定义了所有的网站上的 blog posts 类型。添加 default使 switch无遗漏。在 default 中处理 .screencast 和.course因为他们都是视频。以下是在Swift 4.2中处理 podcasts：enum Post { case tutorial, article, podcast, screencast, course } let podcast = Post.podcast readPost(podcast) // "You are watching a video."上述代码中，你在 default中处理.podcast, 即使它不是视频，Swift不会报警告因为 switch没有遗漏。Swift 5关注到了新增枚举的情况SE-0192(https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md)：func readPost(_ post: BlogPost) -> String { switch post { case .tutorial: return "You are reading a tutorial." case .article: return "You are reading an article." @unknown default: return "You are reading a blog post." } } readPost(screencast) // "You are reading a blog post." readPost(course) // "You are reading a blog post." readPost(podcast) // "You are reading a blog post." 在这段代码中，你标记用@unknown 标记 default，Swift 将会警告你switch是不详尽的。default处理 .screencast, .course和.podcast ，因为他们都是 blog posts。[](https://blog.devorz.com/2019/04/04/What-s-New-in-Swift5/#Adding-Result-to-the-Standard-Library)Adding Result to the Standard LibrarySwift 5 加入 Result到标准库[ [SE-0235]](https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md)：// 1 enum ConnectionError: Error { case noNetwork, noDatabase } // 2 let networkSuccess = Result<String, ConnectionError>.success("Network connected!") let databaseSuccess = Result<String, ConnectionError>.success("Database connected!") let networkFailure = Result<String, ConnectionError>.failure(.noNetwork) let databaseFailure = Result<String, ConnectionError>.failure(.noDatabase) let sameSuccess = networkSuccess == databaseSuccess let sameFailure = networkFailure == databaseFailure let success: Set = [networkSuccess, databaseSuccess] let failure: Set = [networkFailure, databaseFailure] let successDictionary = [ networkSuccess: try! networkSuccess.get(), databaseSuccess: try! databaseSuccess.get() ] let failureDictionary = [ networkFailure: ConnectionError.noNetwork, databaseFailure: ConnectionError.noDatabase ] 这段代码如何工作：声明最常见的连接错误.比较连接结果，将它们添加到集合中。你可以使用这些集和中的元素作为字典的 key ，因为Result实现了 Equatable和 Hashable。Conforming Never to Equatable and HashableSwift 5 是 Never 遵从 Equatable和Hashable[SE-0215] :let alwaysSucceeds = Result<String, Never>.success("Network connected!") let neverFails = Result<String, Never>.success("Database connected!") let alwaysFails = Result<Never, ConnectionError>.failure(.noNetwork) let neverSucceeds = Result<Never, ConnectionError>.failure(.noDatabase) let sameValue = alwaysSucceeds == neverFails let sameError = alwaysFails == neverSucceeds let alwaysSuccess: Set = [alwaysSucceeds, neverFails] let alwaysFailure: Set = [alwaysFails, neverSucceeds] let alwaysSuccessDictionary = [ alwaysSucceeds: try! alwaysSucceeds.get(), neverFails: try! neverFails.get() ] let alwaysFailureDictionary = [ alwaysFails: ConnectionError.noNetwork, neverSucceeds: ConnectionError.noDatabase ] 在此代码中，您定义连接结果的返回值或错误，比较它们，将它们添加到集合并将它们用作字典key。Dynamically Callable TypesSwift 5定义了 dynamically callable types,使其可以与 Python 和 Ruby相互操作。// 1 @dynamicCallable class DynamicFeatures { // 2 func dynamicallyCall(withArguments params: [Int]) -> Int? { guard !params.isEmpty else { return nil } return params.reduce(0, +) } func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? { guard !params.isEmpty else { return nil } return params.reduce(0) { $1.key.isEmpty ? $0 : $0 + $1.value } } } // 3 let features = DynamicFeatures() features() // nil features(3, 4, 5) // 12 features(first: 3, 4, second: 5) // 8上述代码：用 @dynamicCallable 标记 DynamicFeatures 使其成为 dynamically callable type使 DynamicFeatures 遵从 @dynamicCallable ,实现 dynamicallyCall(withArguments:) 和 dynamicallyCall(withKeywordArguments:)。使用平常的语法调用 features，编译器将调用dynamicallyCall(withArguments:) 或者dynamicallyCall(withKeywordArguments:) 。[](https://blog.devorz.com/2019/04/04/What-s-New-in-Swift5/#Swift-Package-Manager-Updates)Swift Package Manager UpdatesSwift 5给 Swift Package Manager添加了一些特性：[](https://blog.devorz.com/2019/04/04/What-s-New-in-Swift5/#Platform-Deployment-Settings)Platform Deployment SettingsSwift 5允许您在 Package.swift 中定义所需的最低平台部署目标版本SE-0236(https://github.com/apple/swift-evolution/blob/master/proposals/0236-package-manager-platform-deployment-settings.md)：let package = Package(name: “Package”, platforms: [ .macOS(.v10_14), .iOS(.v12), .tvOS(.v12), .watchOS(.v5) ])你可以在 SupportedPlatform中使用 macOS（），iOS（），tvOS（）和watchOS（）来设置包所需的最低平台版本。Target Build SettingsSwift 5在 Package.swift 中声明了特定于目标的构建设置。它们自定义包管理器在目标构建期间如何调用构建工具SE-0238(https://github.com/apple/swift-evolution/blob/master/proposals/0238-package-manager-build-settings.md)。Dependency MirroringSwift 5为Swift Package Manager带来了依赖镜像 [SE-0219].swift package config set-mirror --package-url <package> --mirror-url <mirror> 即使原始源不可用或被删除，镜像也允许您访问依赖项。set-mirror使用镜像更新依赖项，后者替换所有其他镜像。使用unset-mirror从依赖项中删除镜像：swift package config unset-mirror --package-url <package> swift package config unset-mirror —mirror-url <mirror> swift package config unset-mirror --allMiscellaneous Bits and PiecesSwift 5还增加了一些其他急需的功能和改进：[](https://blog.devorz.com/2019/04/04/What-s-New-in-Swift5/#Making-Codable-Ranges)Making Codable RangesSwift 5 给 rangs 增加了 Codable 的遵从 [SE-0239]:let temperature = 0...10 let encoder = JSONEncoder() let data = try! encoder.encode(temperature) let decoder = JSONDecoder() let temperatureRange = try! decoder.decode(ClosedRange<Int>.self, from: data) 您使用JSONEncoder对温度进行编码并使用JSONDecoder解码数据，因为默认情况下,rangs 在Swift 5中实现了 Codable 。Flattening Nested OptionalsSwift 4.2 使用 try?创建 Nested Optionals：extension Int { // 1 enum DivisionError: Error { case divisionByZero } // 2 func divideBy(_ number: Int) throws -> Int { guard number != 0 else { throw DivisionError.divisionByZero } return self / number } } // 3 let number: Int? = 10 let division = try? number?.divideBy(2) if let division = division,let final = division { print(final) }上述代码：用 DivisionError 扩展 Int 。如果 number为 0 ,divideBy(_:) 抛出 .divisionByZero。解开 division 两次，因为它是一个 Int ?? 型。Swift 5以不同的方式处理[SE-0230]:if let division = division { print(division) }try? 在Swift 5中不会创建 nested optionals ，因此你需要解包一次，因为它是一个 Int？型Removing Customization Points From Collections在Swift 4.2 你入口可以从 Collection中自定义细节(customization points)：extension Array { var first: Element? { return !isEmpty ? self[count - 1] : nil } var last: Element? { return !isEmpty ? self[0] : nil } } let names = ["Cosmin", "Oana", "Sclip", "Nori"] names.first // "Nori" names.last // "Cosmin"在此代码中，first 从 names返回最后一个名字，last 返回数组的第一个元素。两个计算属性都不能按预期工作，因此 Swift 5 会从集合中删除自定义细节 [SE-0232] 。[](https://blog.devorz.com/2019/04/04/What-s-New-in-Swift5/#Identity-Key-Paths)Identity Key PathsSwift 4.2使用.self来访问值:class Tutorial { let title: String let author: String init(title: String, author: String) { self.title = title self.author = author } } var tutorial = Tutorial(title: "What's New in Swift 5.0?", author: "Cosmin Pupaza") tutorial.self = Tutorial(title: "What's New in Swift 5?", author: "Cosmin Pupăză")在此代码中，你使用 .self 一口气更改教程的标题和作者。Swift 5 添加为 identity key paths 来访问值 [SE-0227];tutorial[keyPath: \.self] = Tutorial( title: "What's New in Swift 5?", author: "Cosmin Pupăză")在此代码中，您使用\ .self来更新 tutorial。Initializing Literals Through Coercion在Swift 5中，如果类型符合 literal 协议，则literal initializers会将字面值强制转换为其类型:let value = UInt64(0xFFFF_FFFF_FFFF_FFFF) 在Swift 4.2中，上面的代码行在编译时产生溢出错误。Build Configuration UpdatesSwift 4.2 条件编译时用 >=： let favoriteNumber = 10 var evenNumber = true #if !swift(>=5) evenNumber = favoriteNumber % 2 == 0 #else evenNumber = favoriteNumber.isMultiple(of: 2) #endif #if !compiler(>=5) evenNumber = favoriteNumber % 2 == 0 #else evenNumber = favoriteNumber.isMultiple(of: 2) #endif 这些条件检查Swift版本是否大于或等于5，并在满足条件时编译这些代码。Swift 5增加 < 用来更简洁的表达条件 [SE-0224]:#if swift(<5) evenNumber = favoriteNumber % 2 == 0 #else evenNumber = favoriteNumber.isMultiple(of: 2) #endif #if compiler(<5) evenNumber = favoriteNumber % 2 == 0 #else evenNumber = favoriteNumber.isMultiple(of: 2) #endifUsing Variadic Parameters for Enumeration Cases With Associated ValuesSwift 4.2中，在有关联值时，你可以使用可变参数来枚举case:enum BlogPost { case tutorial(_: String...) case article(_: String...) }您可以使用 String ... 获取 tutorials 和 articles 详细信息。这在 Swift 5 中是不行的，所以你应该使用数组：enum BlogPost { case tutorial([String]) case article([String]) }Deprecating String Index Encoded OffsetsSwift 4.2字符串使用UTF-16编码。因此，encodedOffset将返回UTF-16字符串的偏移量：let swiftVersion = "Swift 4.2" let offset = swiftVersion.endIndex.encodedOffset 在这段代码 你可以获得 swiftVersion 中 endIndex 的偏移量。这不适用于Swift 5中使用的UTF-8字符串编码，因此Swift 5用utf16Offset（in :)替换encodedOffset来处理这两种情况:let swiftVersion = "Swift 5" let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion) New Pointer MethodsSwift 5添加了 ContiguousStorageIfAvailable（_ :)到 Sequence 和 withContiguousMutableStorageIfAvailable（_ :) 到 MutableCollection，为协议扩展中的 withUnsafeBufferPointer（_ :)和withUnsafeMutableBufferPointer（_ :)提供通用实现[SE-0237]。SIMD Vector UpdatesSwift 5将处理器的SIMD类型操作添加到标准库中。它们为 SIMD vectors 和矩阵提供底层支持。它们还简化了<simd/simd.h>的Objective-C，C和C实现[SE-0229].
翻译自Raywenderlich的文章：What’s New in Swift 4? Swift 4 是Swift下一个大版本更新，预计在2017年秋推出Beta版。其重点在与Swift3的兼容性和ABI稳定性。 本文重点介绍一些这个版本中显著影响你代码的变化。好了，让我们开始吧。Getting StartedSwift 4包含在Xcode 9。你可以从苹果的developer portal下载最新的Xcode9(需要一个有效的开发者账号)。每个Beta版的Xcode 都会捆绑最新的Swift 4 快照。在你阅读时，你会注意到[SE-xxxx]格式的链接，这些链接将带你进去相关的Swift Evolution提案。如果你想获得很多和主题相关的信息，请务必点开它们。我推荐在Playground中尝试Swift 4 的新特性和更新，这有助于在脑海中巩固知识，并使你深入每个主题。把玩这些例子，并尝试扩展／打断他们。玩得开心。注：这篇文件将在为每个Bate版的Xcode更新，如果你使用的是不同的Swift快照，这些代码不保证可以运行。Migrating to Swift 4从Swift 3到 4 的迁移比 Swift 2.2 到 3 少了很多麻烦，一般来说，绝大多数的改变都是附加的，并不需要大量的人为修改。因此，Swift 迁移工具为你处理多数的改变。 Xcode 9 同时支持 Swift 4 和 Swift 3.2。如果需要，你可以逐个指定项目中的每个 target 使用 Swift 3.2 还是 Swift 4。 迁移到 并不是完全不受限制的，你可能需要重新部分代码来兼容修的 SDK .而且由于 Swift ABI 仍然尚未稳定所以你需要用 Xcode 9 重新编译你依赖。 当你准备迁移到 Swift 4 ， Xcode 9 再次提供一个工具来帮助你，你可以点击Edit/Convert/To Current Swift Syntax…来呼出转换工具。 选择你想要转换的 targets 后，Xcode 将提示你 Objective-C 推导中的偏好， 选择推荐的选项通过限制推断来减少你的二进制文件的大小。（关于这个的主题的更多信息，参阅Limiting @objc Inference）. 为了更好地理解你代码中预期的变化，我们将首先介绍Swift 4 中变更的API。API Changes在跳转到 Swift 4 加入的新特新前，让我们首先来看一下它对现有的 API 有哪些改成／改善。StringSwift 4 中的 String 获得了很多应得的称赞。这个提案包含大量的改变，让我们来分解下[SE-0163]：怕你怀旧，strings 再次变为集合像之前的 Swift2.0 一样。这个改变使你不在依赖 characters数组，现在你直接遍历 String 对象 ：let galaxy = "Milky Way ?" for char in galaxy { print(char) }不仅仅是循环，你还可以获得 Sequence 和 Collection 所有附加功能;galaxy.count // 11 galaxy.isEmpty // false galaxy.dropFirst() // "ilky Way ?" String(galaxy.reversed()) //"? yaW ykliM" // Filter out any none ASCII characters galaxy.filter { char in let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII }) return isASCII } // "Milky Way " ASCII 的例子演示了对 Character 一个小改进,现在你可以从 Character 直接访问 UnicodeScalarView,以前你需要实例化一个新的 String[SE-0178]。还有一个附加的时候 StringProtocol, 它声明以前在 String 上声明的大部分功能。这种变化的原因是为了改善切片工作。Swift 4 在 String 中增加了 Substring 类型，继承自 StringProtocol。String 和 Substring 都实现了 StringProtocol,这是两者几乎拥有相同的功能。// Grab a subsequence of String let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3) var milkSubstring = galaxy[galaxy.startIndex...endIndex] // "Milk" type(of: milkSubstring) // Substring.Type // Concatenate a String onto a Substring milkSubstring += "?" // "Milk?" // Create a String from a Substring let milkString = String(milkSubstring) // "Milk?" 另一项改进是 String 对字形簇的解读，这个决议来自于Unicode 9的改编。在这之前，多码点（multiple code points）构成的 unicode 字符的 count 结果大于1，这通常发生在选定肤色的emoji表情，下面是一些前后对比："?‍?".count // Now: 1, Before: 2 "??".count // Now: 1, Before: 2 "?‍❤️‍?‍?".count // Now: 1, Before, 4 这只是[String Manifesto]的一部分，你可以阅读预期改动的原始动机和被提议后的答复。Dictionary 和 Set至于 Collection 类型，Set 和 Dictionary 不是很直观，幸运的是，Swift 团队给了它们很多必要的关照 [SE-0165].Sequence Based Initialization第一个是可以创建一个dictionary 从序列的键值对（元组）：let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"] let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78] // Dictionary from sequence of keys-values let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances)) // ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]Duplicate Key Resolution现在你可以用喜欢的方式复制 keys 实例化一个 dictionary，这使我们不需要写繁琐的键值对关系语句：// Random vote of people's favorite stars let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"] // Merging keys with closure for conflicts let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1] 上面的代码使用了 zip 和 缩写 + 处理重复的 keys 和冲突的 values 。注： 如果你不熟悉 zip 你可以快速的在Apple的 [Swift Documentation] 学习它。FilteringDictionary 和 Set 现在可以筛选将结果赋给一个新的同类型对象。// Filtering results into dictionary rather than array of tuples let closeStars = starDistanceDict.filter { $0.value < 5.0 } closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37] Dictionary MappingDictionary 获得了一个非常有用的方法用来 map 它的 values：// Mapping values directly resulting in a dictionary let mappedCloseStars = closeStars.mapValues { "\($0)" } mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"] Dictionary Default Values访问 Dictionary 的 value 时，常见的做法是使用 nil coalescing operator 来给一个默认值（译者注 ：a != nil ? a! : b 的方式，见[Basic Operators]。在 Swift 4 现在有了简洁的多的方式： // Subscript with a default value let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown" // Subscript with a default value used for mutating var starWordsCount: [String: Int] = [:] for starName in nearestStarNames { let numWords = starName.split(separator: " ").count starWordsCount[starName, default: 0] += numWords // Amazing } starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]以前这种住转变需要包裹在臃肿的 if-let 语句中，在 Swift 4 中可能只需要一行。Dictionary Grouping另一个出奇有用的附加功能可以从 Sequence 实例化一个 Dictionary ，并将其分组：// Grouping sequences by computed key let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! } // ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]] 当对特定的数据进行归类时会变得很方便。Reserving CapacitySequence 和 Dictionary 现在都具备保留容量的功能。// Improved Set/Dictionary capacity reservation starWordsCount.capacity // 6 starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity starWordsCount.capacity // 24 在这些类型中，重新分配是代价高昂的任务。使用 reserveCapacity(_:) 可以很容易的改善执行效率。 这是一个巨大的改变，所以务必检查这两个类型，想办法使用这些新特性来优化你的代码。Private Access Modifier在 Swift 3 上大家并不是很喜欢加入的 fileprivate, 理论上，它很不错，但是实践中它的用法时常让人困惑。在成员内部使用 private,当你在相同的文件共享成员变量的访问时很少使用 fileprivate。 问题是 Swift 鼓励使用 extensions 来将代码按逻辑分组。extension 在成员变量的原始作用域之外，导致广泛地需要使用 fileprivate 。 Swift 4 意识到上述在类型和 extension 共享访问权的初衷，但是它只在相同的源文件中有效 [SE-0169]: struct SpaceCraft { private let warpCode: String init(warpCode: String) { self.warpCode = warpCode } } extension SpaceCraft { func goToWarpSpeed(warpCode: String) { if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate print("Do it Scotty!") } } } let enterprise = SpaceCraft(warpCode: "KirkIsCool") //enterprise.warpCode // error: 'warpCode' is inaccessible due to 'private' protection level enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!" 这让你可以实现原本 fileprivate 的目的，而不需要杂乱的代码。API Additions现在让我们一起来看一下 Swift 4 的新特性，这些改变只是一下简单的附加功能，不会破坏你现有的代码。Archival and Serialization目前为止，Swift 中自定义类型的序列化和归档有太多的坑，对于 class 类型，你需要子类化 NSObject 并且实现 NSCoding协议。而像 struct 和 enum 这样的值类型，需要创建一个子类通过扩展 NSObject 和 NSCoding的 hacks 来实现。 Swift 4 解决了这三种类型的序列化问题[SE-0166]:struct CuriosityLog: Codable { enum Discovery: String, Codable { case rock, water, martian } var sol: Int var discoveries: [Discovery] } // Create a log entry for Mars sol 42 let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])上面的例子中，你可以看到在 Swift 中类型的 Encodable 和 Decodable 只需要实现 Codable 协议，如果所有的属性都实现了 Codable 协议，那么编译器将自动完成协议的实现。 对对象进行编码，你需要把它交给一个编码器，Swift 4 开始积极的实现编码器。每个编码器按照不同的 schemes 。（注：该提案的部分内容仍在开发中）：let jsonEncoder = JSONEncoder() // One currently available encoder // Encode the data let jsonData = try jsonEncoder.encode(logSol42) // Create a String from the data let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}" 它将一个对象自编码成 JSON 对象，请务必检查 JSONEncoder 的属性来定制它的输出。过程的最后一部分是解码数据为一个具体对象：let jsonEncoder = JSONEncoder() // One currently available encoder // Encode the data let jsonData = try jsonEncoder.encode(logSol42) // Create a String from the data let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}" 通过使用 Swift 4的 encoding/decoding，获得Swift的类型安全性。同时不依赖 @objc 协议的开销和限制。Key-Value Coding目前为止，由于函数是一个闭包的缘故，你可以在不调用函数的情况下对函数进行引用。你不能做的通过属性访问是没有暴露借口的私有变量。令人兴奋的是 Swift 4 可以用对象的 Key paths 来 get/set 私有变量。struct Lightsaber { enum Color { case blue, green, red } let color: Color } class ForceUser { var name: String var lightsaber: Lightsaber var master: ForceUser? init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) { self.name = name self.lightsaber = lightsaber self.master = master } } let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red)) let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue)) let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)在这里你创建了一些 ForceUser 实例，通过设置他们的 name 、 lightsaber 和 master 。创建 key path ，你只需使用一个反斜杠后面跟上你感兴趣的属性:// Create reference to the ForceUser.name key path let nameKeyPath = \ForceUser.name // Access the value from key path on instance let obiwanName = obiwan[keyPath: nameKeyPath] // "Obi-Wan Kenobi" 在这个例子中，你给 ForceUser 的 name 属性创建了一个 key path。然后使用这个 key path 通过新的下标 keyPath 这个下下标现在在每种类型都可以用。这里有一些通过 key path 访问子对象，设置属性，构建 key path 引用的例子：// Use keypath directly inline and to drill down to sub objects let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color] // blue // Access a property on the object returned by key path let masterKeyPath = \ForceUser.master let anakinMasterName = anakin[keyPath: masterKeyPath]?.name // "Obi-Wan Kenobi" // Change Anakin to the dark side using key path as a setter anakin[keyPath: masterKeyPath] = sidious anakin.master?.name // Darth Sidious // Note: not currently working, but works in some situations // Append a key path to an existing path //let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name) //anakin[keyPath: masterKeyPath] // "Darth Sidious" key path 之美在于它在 Swift 中是坚固的，不像 Objective-C 中的 string 那么凌乱。 ### Multi-line String Literals 创建多行文本是很多编程语言一个非常普遍的特性。Swift 4 加入这个简单但是有用的语法，用三个引号包装文本[\[SE-0168\]](https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md)： let star = "⭐️" let introString = """ A long time ago in a galaxy far, far away.... You could write multi-lined strings without "escaping" single quotes. The indentation of the closing quotes below deside where the text line begins. You can even dynamically add values from properties: \(star) """ print(introString) // prints the string exactly as written above with the value of star当构建 XML/JSON 信息或者 UI 上的长文字排版时，这是极为有用的。One-Sided Ranges为了减少冗余和提高可读性，标准库现在可以使用半开区间来推断开始和结束的索引[SE-0172]。 从集合的一个索引到开始或者结束的索引创建一个区间，有了非常便利的方法：// Collection Subscript var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex] let firstThree = planets[..<4] // Before: planets[planets.startIndex..<4] 如你所见，半开区间不需要指明开始或者结束的索引。Infinite Sequence同时允许你从一个可计算的开始索引定义一个无限的 Sequence：// Infinite range: 1...infinity var numberedPlanets = Array(zip(1..., planets)) print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")] planets.append("Pluto") numberedPlanets = Array(zip(1..., planets)) print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")] Pattern Matching半开区间另一个很好的用法是模式匹配：// Pattern matching func temperature(planetNumber: Int) { switch planetNumber { case ...2: // anything less than or equal to 2 print("Too hot") case 4...: // anything greater than or equal to 4 print("Too cold") default: print("Justtttt right") } } temperature(planetNumber: 3) // Earth Generic Subscripts下标是一种直观且重要的访问数据的方式，为了改善效率，现在可以用在普通类型[SE-0148]：struct GenericDictionary<Key: Hashable, Value> { private var data: [Key: Value] init(data: [Key: Value]) { self.data = data } subscript<T>(key: Key) -> T? { return data[key] as? T } }例子中的返回值是泛型，你可以在这个泛型中这样使用下标：// Dictionary of type: [String: Any] var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1]) // Automatically infers return type without "as? String" let name: String? = earthData["name"] // Automatically infers return type without "as? Int" let population: Int? = earthData["population"]不仅仅是返回值可以是泛型的，下标也可以是泛型的：extension GenericDictionary { subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key { var values: [Value] = [] for key in keys { if let value = data[key] { values.append(value) } } return values } } // Array subscript value let nameAndMoons = earthData"moons", "name" // [1, "Earth"] // Set subscript value let nameAndMoons2 = earthData[Set(["moons", "name"])] // [1, "Earth"]在这个例子中你可以看到，传递两个不同的 Sequence 类型（Array 和 Set）会得到各自的 vlaues 组成的数组。Miscellaneous以上列举的囊括了swift4中最大变化的部分, 现在让我们快速看看其他方面的小改动。MutableCollection.swapAt(_:_:)MutableCollection 现在有 swapAt(_:_:) 方法，正如它的命名，用来交换给定下标的值 [SE-0173]：// Very basic bubble sort with an in-place swap func bubbleSort<T: Comparable>(_ array: [T]) -> [T] { var sortedArray = array for i in 0..<sortedArray.count - 1 { for j in 1..<sortedArray.count { if sortedArray[j-1] > sortedArray[j] { sortedArray.swapAt(j-1, j) // New MutableCollection method } } } return sortedArray } bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4] Associated Type Constraints现在你可以使用 where 从句约束相关的类型[SE-0142]:protocol MyProtocol { associatedtype Element associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element }通过约束协议，associatedtype 声明可以直接限制它们的值,不必在大费周折。Class and Protocol Existential最后一个从 Objective-C 搬过来的特性是可以定义一个类型遵守一类或一组协议[SE-0156]:protocol MyProtocol { } class View { } class ViewSubclass: View, MyProtocol { } class MyClass { var delegate: (View & MyProtocol)? } let myClass = MyClass() //myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?' myClass.delegate = ViewSubclass()Limiting @objc Inference要向 Objective-C 暴露你的 Swift API ，可以使用 @objc 编译标志。在很多情况下编译器可以为你推导。但是大量的推理会导致三个主要的问题:可能显著的增加你的二进制文件大小有时候无法准确的推倒增加构成 Objective-C 方法选择器冲突的风险Swift 4 通过限制 @objc 的推导来解决这个问题，这意味着当你需要 Objective-C 所有的动态调度功能是，你主要明确的使用 @objc。 举几个你需要修改的示例包括 private 方法，动态声明 和 NSObject 基类的一些方法。NSNumber Bridging在很长时间内，NSNumber 和 Swift numbers 有很多奇怪的行为都困扰这这门语言。Swift 4 干掉了这些bug [SE-0170]：这里有一个示范：let n = NSNumber(value: 999) let v = n as? UInt8 // Swift 4: nil, Swift 3: 231 在 Swift 3 中会出现的怪异现象，如果数字溢出，它会简单的从0开始。在这个例子中 999% 2 ^ 8 = 231。Swift 4 解决了这个问题，只有当强转后的类型可以安全的容纳时，才会返回值。Swift Package Manager近几个月，Swift 包管理有许多更新，一些比较大的更新如下：从 branch 或者 comint hash 中获取依赖对可接受的包更多的支配用更为常见的解决方案代替不直观的命令能够定义用来编译的 Swift 版本为每个 target 指明 source files 路径。这些是 SPM 在必经之路上迈出的大步伐，它还有很长的路要走，我们可以通过积极参与提议来帮忙完善它。有关最近已解决的提案的详细描述，请查看Swift 4 Package Manager UpdateStill In Progress在撰写本文时，队列中仍有15个接受的提案。如果你想一睹为快，访问Swift Evolution Proposals然后用 Accepted 筛选.