在翻阅 Stripe 和 Adyen 的 iOS SDK 时，发现他们有很多 public 方法却搜不到调用。仔细看发现它们被 @_spi(InternalName) 标记了。这些支付 SDK 都是模块化的（比如 StripeCore、StripeCard）。 痛点： StripeCard 必须调用 StripeCore 里的某些工具类。如果设为 internal，跨模块调不到；如果设为 public，集成 SDK 的商户就会在代码补全里看到一堆乱七八糟的内部方法，既不安全也显得 SDK 很乱。@_spi可以用来解决这个问题。@_spi 是一个非常强大但属于半私有（Semi-private）的 Swift 特性。它的全称是 Service Provider Interface。简单来说，@_spi 允许你将某些 API 标记为“私有”，但又允许特定的外部模块访问它们。这在管理 SDK 内部模块间通信，或者在分发二进制 SDK 时非常有用。1. 核心作用：精细化访问控制在 Swift 标准的访问控制中，我们只有 open, public, internal, fileprivate, private。如果你设为 internal，宿主 App 看不见，但你自己的其他库（如果是多组件架构）也看不见。如果你设为 public，你自己的其他库能用了，但宿主 App 的开发者也能看见并调用它，这会破坏你的 API 纯洁性。@_spi 解决了这个问题： 它创建了一个“命名的隐私圈”。代码示例在你的 SDK 核心模块中：public struct MySDKConfig { public init() {} // 使用 @_spi 标记内部调试工具，指定组名为 "InternalStuff" @_spi(InternalStuff) public func resetInternalState() { print("只有内部人员能调这个！") } } 在你的另一个内部模块（或测试模块）中：// 普通 import 看不到 resetInternalState import MySDK // 必须使用 @_spi 导入才能解锁对应的 API @_spi(InternalStuff) import MySDK let config = MySDKConfig() config.resetInternalState() // 现在可以调用了 2. 为什么 SDK 开发者需要它？A. 跨组件通信如果你的 SDK 拆分成了多个二进制库（例如 MySDKCore 和 MySDKAdvanced），有些核心方法需要暴露给 Advanced 模块，但不希望普通用户在代码补全中看到它们。B. 单元测试你可能不想把某些内部逻辑设为 public，但又想在测试 Target 中访问它们。使用 @testable import 只能在非二进制（源码）情况下工作，对于 XCFramework，@_spi 是更好的选择。C. 隐藏“实验性”API你可以发布一些处于 Beta 阶段的接口，标记为 @_spi(Experimental)。只告知特定的合作伙伴使用，普通用户不会误触。3. 在 XCFramework 中的表现当你使用 Build Libraries for Distribution 编译 XCFramework 时，Swift 会生成 .swiftinterface 文件。被 @_spi 标记的函数依然会出现在 .swiftinterface 中。但是，除非调用方显式写了 @_spi(GroupName) import ...，否则编译器会当这些函数不存在。这有效地减少了 SDK 的 API 污染。4. 注意事项与风险非正式标准：下划线前缀 _ 意味着这是 Swift 的一个非正式、演进中的特性。虽然苹果自己在 Foundation 和 SwiftUI 中大量使用，但理论上未来可能发生语法变化。安全性有限：这并不是一种加密手段。如果开发者通过查看你的 .swiftinterface 发现了组名，他们依然可以强行通过 @_spi(组名) 来调用。所以它主要是为了 “防止误用” 而不是 “防黑客”。总结作为 SDK 开发者，把 @_spi 看作是一个 “带暗号的公共接口”。它能帮你完美平衡“多模块协作”与“保持对外接口整洁”之间的矛盾。
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
前言苹果已经从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 筛选.
property follows cocoa naming convention for returning ‘owned’ objects问题当做一个修改密码的页面的时候，很自然的把新密码的输入框命名为newPasswordTextField，此时编译不通过。错误提示如下：property follows cocoa naming convention for returning ‘owned’ objects原因Transitioning to ARC Release Notes中写道： You cannot give an accessor a name that begins with new. This in turn means that you can’t, for example, declare a property whose name begins with new unless you specify a different getter// Won't work: @property NSString *newTitle; // Works: @property (getter=theNewTitle) NSString *newTitle; 在MRC时代，有约定如果方法名以alloc、copy、init、mutableCopy和new开头且有返回值时，return 对象时先调用一次retain。ARC时代只是把retain交给编译器去自动添加。所以当属性名以new开头时生成的getter方法会有内存管理的语义冲突。解决办法重命名属性@property (strong, nonatomic)UITextField *theNewPasswordTextField;修改getter方法名@property (strong, nonatomic, getter=theNewPasswordTextField) UITextField *newPasswordTextField;指明 attribute((objc_method_family(none)))#ifndef __has_attribute #define __has_attribute(x) 0 // Compatibility with non-clang compilers #endif #if __has_attribute(objc_method_family) #define BV_OBJC_METHOD_FAMILY_NONE __attribute__((objc_method_family(none))) #else #define BV_OBJC_METHOD_FAMILY_NONE #endif @interface ViewController : UIViewController(UITextField *)newPasswordTextField BV_OBJC_METHOD_FAMILY_NONE;@end
中文译名《编写高质量iOS与OS X代码的52个有效方法》。关于这本书的一些笔记。熟悉Objective-C起源由消息型语言鼻祖Smalltalk演化而来。是C语言的超集。与函数式语言关键区别：消息结构的语言，运行时所执行的代码由运行环境决定(动态绑定)；函数调用的语言，由编译器决定。NSString *someString 中 someString 为指向NSString 的指针。Type *声明的对象总是被分配的堆区(heap space) 中，而绝不可能分配的栈区(stack)。如果只需保存int float double char 等非对象类型（nonoobject type），使用C结构体(如CGRect)比使用OC对象性能更优，因为创建OC对象需要分配及释放堆区内存等额外开销。在类的头文件中尽量少引入其他头文件头文件在类的头文件中，少引入头文件，使用@Class 向前声明即可。 然后在实现文件中再引入那些类的头文件。这样可以降低类之间的耦合。无法使用向前声明的情况，如果 引入当前类遵守的某一项协议，此时把协议单独放下一个头文件中，再将其引入。多用字面量语法，少用与之等价的方法。NSNumber *intNumber = @1; NSNumber *floatNumber = @2.5f; NSArray *namesArray = @[@"张三",@"李四",@"王五"]; NSString *myName = namesArray[0]; NSDictionary *personDict = @{@"Name":@"张建宇"} NSString *name= personDict[@"name"]; 多用类型常量，少用＃define 预处理指令。＃define定义的出来的常量，不包含类型信息，编译器需要再编译前执行查找替换，会增加编译时间。只在当前类使用的常量，可以使用 static const 在.m中定义。在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。命名时，通常使用于是相关的类名最为前缀。如UITextFieldTextDidBeginEditingNotification。使用枚举来表示状态，选项，状态码易懂的枚举名可以提高代码的可读性。选项为多选时，使用二进制表示枚举值，以便通过按位或将其组合起来。?：UIInterfaceOrientationMask。switch语句中，不要实现default分支。这样加入新的枚举之后，编译器会提示：switch语句没有处理所有枚举值。消息／对象／运行时理解“属性” 这一概念在对象内部尽量直接访问实例变量。直接访问实例变量不经过方法派发，比点语法快。⚠️直接访问copy修饰的属性，不会掉用setter方法，所以不会拷贝该属性。⚠️不会触发KVO通知。是否会产生问题，视情况而定。初始化方法和dealloc方法中，总是应该直接访问。⚠️注意考虑懒加载的情况下，要使用点语法。理解“对象等同性”这一概念。==比较的是指针地址是否一样。特有的等同性判断方法，如isEqualToString比isEqual快一些。相同的对象必须具有相同的哈希码，但是哈希码相同的对象未必相同。不必逐条比较每个属性的哈希码，依情况，使用速度快，碰撞率低的算法。以“类族模式”隐藏实现细节在既有类中使用关联对象存放自定义数据理解objc_msgSend 的作用Objective-c “动态绑定(dynamic binding)” 的特性使得所要调用的函数直到运行时才能确定。通过动态消息派发系统(dynamic message dicpatch system)来查出对应的方法并执行。消息由接收者(receiver)，选择子(selecter)，以及参数构成。调用方法实际上相当于在给对象发送消息。理解消息转发机制用方法调配技术调试黑盒方法 //TODO//Category #import "NSString+Test.h" #import <objc/runtime.h> @implementation NSString (Test) - (NSString*)eoc_myLowercaseString { NSString *lowercase = [self eoc_myLowercaseString]; NSLog(@"%@ => %@", self, lowercase); return lowercase; } // 不会死循环，因为eoc_myLowercaseString与lowercaseString方法互换了，运行时期间eoc_myLowercaseString选择子实际上对应的原有的lowercaseString方法实现。 + (void)load{ Method originalMethod = class_getInstanceMethod([NSString class],@selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class],@selector(eoc_myLowercaseString)); method_exchangeImplementations(originalMethod, swappedMethod); } @end /* 测试一下： NSString *string = @"Hello World"; NSString *textString = [string lowercaseString]; Output: Hello World => hello world */理解类对象的用意 //TODO接口与API设计使用前缀避免命名空间冲突。提供全能初始化方法提供一个全能初始化方法，并与文档中指明，其他初始化方法均调用此方法。全能初始化方法与超类不同时，应该重写，冲突时，应该加上抛异常机制提示。实现description方法自定义类，覆写description方法，返回的字符串，NSLog时的显示该字符串；重写debugDescription，改变调试控制台输出信息，默认为description方法的返回值。尽量使用不可变对象。使用清晰而且协调的命名方式为私有方法名加前缀给私有方法名加上前缀不要使用下划线做私有方法的前缀，因为这种做法是预留给苹果的。理解Objective-C错误模型只有发生使整个应用崩溃的严重错误时，才之用异常。不那么严重的错误，可以指派委托方法来处理错误，也可以把错误信息放在NSError里，由NSLog输出。理解NSCoding协议。想令对象具有拷贝功能，需实现NSCoying协议；如果对象有可变版本，还需要实现NSMutableCopying协议。浅拷贝之后的内容与原始内容指针地址相同，深拷贝之后的内容指向原始内容相关对象的一份拷贝。(深拷贝会逐个元素发送Copy消息，用拷贝得到元素创建Set);通过委托与数据源协议进行对象间的通信。将类的实现代码分散到便于管理的数个分类之中。//易于管理总是为第三方类的分类名称增加前缀。勿在分类中声明属性。封装数据所用的全部属性都定义在主接口里。关联对象可以解决分类不能合成实例变量的问题。使用“class-continuation”分类 隐藏实现细节 //就是Extension吧，匿名的分类。通过协议提供匿名对象。 //隐藏返回值类型Block与GCD翻译成 块与大中枢派发也是醉了。。为常见的block类型创建typedef使用handler block降低代码的分散程度。//如AFN中的网络请求回调用block引用其所属对象时不要出现循环引用。多用派发队列，少用同步锁。派发队列可以用来表述同步语义(synchornization semantic),比使用@synchronized块和NSLock对象更简单。使用同步和异步派发结合起来，可以实现与普通加锁一样的同步行为，而且不会阻塞执行异步派发的线程。使用同步队列和栅栏块，可以让同步锁更加高效。多用GCD,少用｀performSelector｀系列方法。SEL selector; if(/* some condition */){ selector = @selector(foo); }else if(/* some other condition */){ selector = @selector(bar); }else{ selector = @selecter(baz) } /* 运行时才能确定调用列哪个方法， 编译器没有办法在编译时按ARC的内存管理原则判定返回值是不是该释放， 编译器没有添加释放操作。 这将又可能导致内存泄漏 */掌握GCD和NSOperationQueue的使用时机运行任务之前，可以用在NSOperation对象上调用cancel取消该任务。可以指定任务间的依赖关系。可以通过KVO监听NSOperation的属性。例如isFinished,isCancelled。指定操作的优先级。重用NSOperation对象。通过Dispathtch Group机制，根据系统资源状况来执行任务。使用dispatch_once来执行只需运行一次的线程安全代码。+(id)sharedInstance{ static SomeClass *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(@onceToken,^{ sharedInstance = [[self alloc]init]; }) return sharedInstance; }不要使用dispatch_get_current_queue(void);//调试还是可以用下的系统框架熟悉系统框架。CFNetWork:提供C语言级别的网络通信能力，将BSDsocket抽象成易于使用的接口。CoreAudio:提供的C语言API可用来操作设备上的音频硬件。AVFoundation:处理音频视频的录制，回放。CoreData:用于实现数据持久化。CoreText:提供C语言接口，用于执行文字排版以及渲染操作。多用block枚举，少用for循环。//block枚举本身通过GCD并发执行遍历，更高效。对自定义其内存管理语义的cellection使用无缝桥接。构建缓存时选用NSCache而非NSDictionary.// low memory时 NSCache会自动删减缓存精简initalize和load的实现代码。iOS平台上，当类或者分类载入系统时，会先调用+(void) load;同时实现时，先调类里面的，再调用分类里面的。无法判断各个类载入的顺序，所以在load方法使用第三方类比较危险。load方法会阻塞线程，所以要尽量精简里面的代码。首次使用某个类是，会调用该类的+(void) initalize方法，可以覆写该方法做一些与类相关的初始化操作。但是同load一样，也应该尽量精简。别忘了NSTimer会保留目标对象。