好消息!Swift 5 最终正式版在Xcode 10.2 中可使用。这个版本带来稳定的ABI和对一些期待已久的特性的完善。
这个教程中,你将学到关于Swift 5重要的改变。Swift 5需要Xcode 10.2,所以在开始之前确定你已经安装了Xcode 10.2.
开始
Swift 5 是源码兼容Swift 4.2,但不是二进制兼容Swift之前的版本。不过由于稳定的ABI在未来的版本中将是二进制兼容Swift 5。
ABI稳定能够二进制兼容不同Swift版本在app和库之间编译。Swift标准库和runtime被嵌入到操作系统中,所以在任何平台app在发布的时候不需要拷贝这些库。这样就更好的实现工具去耦合和系统集成。
你也需要稳定的ABI去发布二进制库在多个Swift版本中工作。这需要模块格式稳定,稳定的模块文件包含编译者库的公共接口。
在教程的每个章节你将发现Swift进展提议号如[SE-0001].通过浏览每个提议链接你将能学到更多关于Swift5 新的修改。
最好的方法是跟着这个教程试着在playground中尝试这些新的特性。
启动Xcode 10.2 并选择File->New->Playground.设置平台为iOS,模板选择Blank。命名并保存在任意你想保存的地方。是时候开始了。
注:需要快速回忆Swif 4.2 更新了什么?查看Swift 4.2 链接教程:What’s New in Swift 4.2?
语言改进
在Swift 5中有许多语言特性,例如动态可赎回类型,操作未来枚举等等。
测试复数
在Swift 4.2中,你确定一个数是偶数还是奇数,正常是如下操作:
1 | let firstNumber = 4 |
这段代码如何完成工作的:
- 1.检查 secondNumber 不为0.
- 2.检查 firstNumber 除 secondNumber 返回余数是否为0.
- 3.执行除操作。
你必须检查 secondNumber 不为0. 如果为0使用%将为抛出错误。
Swift 5简化了实现通过添加 isMultiple(of:) 到 BinaryInteger [SE-0225]:
1 | if firstNumber.isMultiple(of: secondNumber) { |
isMultiple(of:) 改方法甚至你传0也能正常工作。返回结果变的更干净整洁。
转义原生字符串
Swift 4.2 在字符串中表示反斜杠和引号需要使用转义序列:
1 | let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2." |
Swift 5 添加源字符串。你只要在字符串头部和尾部添加#,这样使用反斜杠和引号就不会有任何问题了[SE-0200]:
1 |
|
当要在字符串中插入字符串,你必须在反斜杠后面添加#号:
1 | let track = "Nothing Else Matters" |
有一些情况是当你需要在字符串开头与结尾使用多个#号:
1 | let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."## |
在上面的代码中,你添加##在字符串头尾做标志,这样你能表示#号里面的字符串。字符串开头的#号数量必须和结尾的#号数量想匹配。
在Swift 4.2,你转义反斜杠在正则表达式表示如下:
1 | // 1 |
这些代码如何工作的:
- 声明versions 定义range 覆盖整个versions字符串
- 定义正则表达式来匹配所有在versions中Swift次要版本
- 使用matches(in:options:range:)方法来匹配次要版本
- 使用ranges从versions中获取次要版本
Swift 5 简化了原生字符串的正则表达式:
1 | let regex = try! NSRegularExpression(pattern: #"\d\.\d"#) |
在这个代码中,你少些了一半的反斜杠,因为在原生字符串中你不需要转义反斜杠。
使用新的字符属性
Swift 4.2 当这种一般的字符串操作任务需要很多操作代码:
1 | let id = "ID10" |
在这个代码中,通过对每一个字符转为整型你想确定id字符串中有多少个数字。
然而,Swift 5 添加为字符添加了属性使得字符更容易使用[SE-0221]:
1 | id.forEach { digits += $0.isNumber ? 1 : 0 } |
在这个示例中,你使用isNumber去检测是否每个字符都为数字。看一看新添加其他你能使用的属性。
使用新的Unicode Scalar 属性
在 Swift 4.2,你实现文本处理使用Unicode scalar算法如下:
1 | let username = "bond007" |
在这个代码里,通过检测每个字符的unicode编码来表示小写或者大写的字母计算username中有多少个字母。
Swift 5 添加了Unicode编码属性,简化了文本处理[SE-0211]:
1 | username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 } |
这段代码中,你使用isAlphabetic属性来检测每个字符是否为字母,这个提案链接展示了所有你能查看的属性。
移除子序列
在Swift 4.2 从序列中返回子序列需要自定义方法如下:
1 | extension Sequence { |
在这个代码中,如果s是一个数字或者是最后一个元素调用remove(_:)方法来移除最后的n个元素。
Swift 5 替换 SubSequence 为具体的类型在序列中[SE-0234]:
1 | extension Sequence { |
在这个代码中,remove(_ :)方法返回的是[Element]类型,因为 dropLast()和 dropLast(_:)方法返回的是[Element]类型。
字典的更新
Swift 5 带来了我们期待已久对字典的改进:
压缩字典
Swift 4.2 使用 mapValues,filter 和 reduce 去过滤 nil值从字典中如下:
1 | let students = ["Oana": "10", "Nori": "ten"] |
这个代码使用mapValues、filter和reduce 来确定学生的有效等级。两种处理需要多个字典传递,使你代码复杂化了。
Swift 5 使用compactMapValues(_:)方法来更搞笑的处理[SE-0218]:
1 | let mapStudents = students.compactMapValues(Int.init) |
完成相同的事情使用更少的代码,整洁!
重命名字典字面值
Swift 4.2 使用 DictionaryLiteral 类型来声明字典如下:
1 | let pets: DictionaryLiteral = ["dog": "Sclip", "cat": "Peti"] |
DictionaryLiteral 不是一个字典或字面值,它是一个键值对表。
Swift 5 重命名 DictionaryLiteral 为 KeyValuePairs [SE-0214]:
1 | let pets: KeyValuePairs = ["dog": "Sclip", "cat": "Peti"] |
Numeric协议更新
Swift 4.2 实现vectors的Numeric协议:
1 | // 1 |
这些代码的工作流程如下:
- 为Vector声明 x ,y 属性和 init(_ :_ :) 构造方法。
- 实现 init(integerLiteral:) 为了Numeric协议需要Vector遵循ExpressibleByIntegerLiteral协议。
- Vector 遵循Numeric协议通过定义magnitude变量,声明init(exactly:)方法,实现+(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:), *(lhs:rhs:), *=(lhs:rhs:).等方法。
- Vector遵循CustomStringConvertible协议来实现description变量。
以上的代码能够使得你使用vectors更容易实现一些操作:
1 | var first = Vector(1, 2) // (1,2) |
Swift 5 实现AdditiveArithmetic协议为vectors,因为你不能定义2D矢量的向量积[SE-0233],它不需要实现ExpressibleByIntegerLiteral协议。
1 | extension Vector: AdditiveArithmetic { |
这个代码里,Vector遵循了AdditiveArithmetic协议,定义了zero属性,实现了+(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:)方法。
字符串插入更新
Swift 4.2 实现字符串插入通过分割字符串插入:
1 | let language = "Swift" |
这段代码,编译器首先进行逐个分割然后通过init(stringInterpolationSegment:)方法插入,然后在将所有分割进行组合通过init(stringInterpolation:)方法。
Swift 5 通过不同的方法进行实现[SE-0228]:
1 | // 1 |
这些代码工作如下:
- 定义一个 DefaultStringInterpolation 类型的实例通过确定的容量和插入数量值。
- 调用 appendLiteral(_ :) 或者 appendInterpolation(_ :) 去添加字面量和插入值到interpolation。
- 生成最终的插入字符串通过调用init(stringInterpolation:)方法。
枚举的新操作
Swift 4.2 不需要处理新添加的枚举类,因为如下所示:
1 | // 1 |
以上代码执行如下操作:
- 定义网站上所有博客文章类型
- 执行switch穷举,添加default。
- 处理 .screencast 和 .course 用default 因为 screencasts 和 courses 是 视频类型。
在Swift 4.2中处理 podcasts类型操作如下:
1 | enum Post { |
在这段代码中,你处理 .podcast 使用default,虽然podcasts 不是视频类型,Swift 4.2 不会警告你因为switch被穷举了。
Swift 5 中新增了这种情况类型[SE-0192]:
1 | func readPost(_ post: BlogPost) -> String { |
在这段代码中,你标记default为@unknown,Swift会警告提示你switch并未穷举,default处理.screencast,.course 和 .podcast 因为screencasts,courses和podcasts是博客文章。
新增Result类再标准库中
Swift 5 添加 Result 类到标准库[SE-0235]:
1 | // 1 |
这段代码执行效果如下:
- 声明一个普通的连接错误枚举
- 比较连接结果,添加他们到集合中,用这些集合作为字典的key值,因为Result实现了Equatable和Hashable协议。
Never 遵循 Equatable和Hashable协议
Swift 5 让Never遵循Equatable 和 Hashable协议[SE-0215]:
1 | let alwaysSucceeds = Result<String, Never>.success("Network connected!") |
在这段代码中,你定义连接结果为总返回有值或者错误,比较他们,添加他们到集合中,用他们做字典的key。
动态调用类型
Swift 5 定义动态调用类型交互操作脚本语言类似Python和Ruby[Se-2016]:
1 | // 1 |
以上代码工作如下:
- 标记DynamicFeatures类为@dynamicCallable使他为动态调用类型.
- 让DynamicFeatures类遵循@dynamicCallable,实现dynamicallyCall(withArguments:)和 dynamicallyCall(withKeywordArguments:)方法。
- 按常规语法去使用features,编译器会调用dynamicallyCall(withArguments:)和 dynamicallyCall(withKeywordArguments:)方法。
Swift包管理更新
Swift 5 添加一些新的特性到Swift包管理中:
平台部署设置:
Swift 5 在Package.swift中允许你定义目标版本的需要的平台版本最小值:
1 | let package = Package(name: “Package”, platforms: [ |
你使用 macOS(), iOS(), tvOS() 和 watchOS() 这些支持的平台设置最低需要的版本在package中。
目标编译设置
Swift 5 声明 目标特性编译设置在Package.swift,他们自定义如果管理调用编译工具在目标编译期间[SE-0238].
镜像依赖
Swift 5 带来了镜像依赖在Swift包管理中[SE-0219].
1 | swift package config set-mirror --package-url <package> --mirror-url <mirror> |
镜像能够让你获得依赖库,甚至原始资源不可获取或是被删除了。
set-mirror 更新依赖库通过镜像,替换其他所有的依赖。
使用 unset-mirror 去移除镜像依赖库:
1 | swift package config unset-mirror --package-url <package> |
各种零碎的东西
Swift 5添加了一些其他大量的特性和改进:
添加Codable Ranges
Swift 5 添加 Codable协议支持ranges[SE-0239]:
1 | let temperature = 0...10 |
你使用JSONEncoder将temperature进行编码,使用JSONDecoder对data进行解码,因为Swift 5默认ranges实现了Codable协议。
整合内嵌可选类型
Swift 4.2 创建内嵌可以选类型使用try?:
1 | extension Int { |
这段代码操作如下:
- 扩展Int添加DivisionError枚举
- divideBy(_:) 方法如果number为0抛出.divisionByZero
- 两次解包division,因为他是一个Int??类型
Swift 5 不同的方式处理[SE-0230]:
1 | if let division = division { |
try? 在Swift 5 不会创建内嵌可选值,所以你只要解包division一次就够他是Int?类型。
从集合中移除自定义的位置值
在Swift 4.2中你从集合中可以使用定制的位置值:
1 | extension Array { |
在这段代码中,first返回names数组最后一个元素,last返回数组第一个元素。
两种计算属性被期望不能使用,所以Swift 5从集合中将他们移除[SE-0232].
Identity Key Paths
Swift 4.2 使用.self获取值:
1 | class Tutorial { |
这段代码,你用.self 一下子修改tutorial的title和author.
Swift 5 添加了 identity key paths来使用[SE-0227]:
1 | tutorial[keyPath: \.self] = Tutorial( |
这段代码里,你用.self 去更新tutorial
强制初始化字面值
在Swift 5,字面值初始化如果类型符合字面值协议强制字面值类型[SE-0213]:
1 | let value = UInt64(0xFFFF_FFFF_FFFF_FFFF) |
在Swift 4.2中,上面这行代码在编译时会报溢出错误。
编译配置更新
Swift 4.2 使用 >= 做编译条件:
1 | let favoriteNumber = 10 |
这些条件判断Swift版本是否大于等于5,如果条件符合就编译这些代码。
Swift 5 添加 < 来简化条件[SE-0224]:
1 | #if swift(<5) |
使用可变参数作为枚举的关联值
你能够使用可变参数作为枚举case的关联值在Swift 4.2中:
1 | enum BlogPost { |
你使用String…做为教程和文章的详情,在Swift 5中已经不能使用,你需要使用数组替代了:
1 | enum BlogPost { |
这次你使用[String]设置教程和文章详情。
取消字符串索引编码偏移
Swift 4.2 字符串是使用UTF-16编码,结果encodeOffset返回一个UIF-16的字符串偏移:
1 | let swiftVersion = "Swift 4.2" |
这里得到swiftVersion的endIndex的Offset,在Swift 5中使用UIF-8编码是不起作用的,所以Swift 5替换encodedOffset为utf16Offset(in:)去处理这个情况[SE-0241]:
1 | let swiftVersion = "Swift 5" |
新指针方法
Swift 5 添加 withContiguousStorageIfAvailable(_ :)到Sequence 和 withContiguousMutableStorageIfAvailable(_ :) 到 MutableCollection 提供通用的实现withUnsafeBufferPointer(_ :) 和withUnsafeMutableBufferPointer(_ :)方法在协议扩展中[SE-0237].
SIMD 矢量更新
Swift 5 增加操作在SIMD类型处理器在标准库中。他们为SIMD矢量和矩阵提供低等级的支持。他们也简化了Objective-C,C和C++的实现在<simd/simd.h>头文件里[SE-0229].
尾声
翻译的水平有限,有错误的地方多多指出,翻译的不好还请谅解。。。Thanks!