Swift 4.2 新特性(译)
Posted 崔小花o
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 4.2 新特性(译)相关的知识,希望对你有一定的参考价值。
一、概述
Swift 4.2 在 Xcode 10 beta 版上可以使用了,在 Swift 4.1 的基础上更新了很多语言特性,为 Swift 5 中 ABI 稳定做好准备。
这篇文章包含了 Swift 4.2 中的重大的改变。因为 Swift 4.2 需要 Xcode 10,所以请下载安装最新的 Xcode 测试版本。
二、准备
Swift 4.2 和 Swift 4.1 源码兼容,但是和其他发布版本的二进制不兼容。Swift 4.2 是 Swift 5 实现 ABI 稳定(不同的 Swift 版本编译的应用程序和库之间实现兼容)的一个中间阶段。ABI 的特性在集成进最终的 ABI 之前会接收社区的大量反馈。
三、语言演进
在这个版本中有很多新的语言特性。例如,随机数生成,动态成员查找等等
3.1 随机数生成
3.1.1 随机数生成
arc4random_uniform(_: )
返回一个 0 - 9 之间的随机数字。这种实现方式有两个问题:
- 需要引入
Foundation
框架,在 Linux 下无法工作。 - Linux上的随机数生成会产生模偏差(有取模的过程,更容易随机到小的数)。
// Swift 4.1
let digit = Int(arc4random_uniform(10))
Swift 4.2 在标准库中添加了随机数的 API SE-0202
/// Swift 4.2
// 1
let digit = Int.random(in: 0..<10)
// 2
if let anotherDigit = (0..<10).randomElement()
print(anotherDigit)
else
print("Empty range.")
// 3
let double = Double.random(in: 0..<1)
let float = Float.random(in: 0..<1)
let cgFloat = CGFloat.random(in: 0..<1)
let bool = Bool.random()
注:randomElement() 如果 range 是空,返回 nil
3.1.2 数组随机
Swift 4.1 数组随机也是采用 C 函数的形式,这种方式会存在上面提到的问题,而且会存在 Int 和 Int32 转换的问题。
/// swift 4.1
let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
let index = Int(arc4random_uniform(UInt32(playlist.count)))
let song = playlist[index]
Swift 4.2 采用了更加简单直接的方式。
/// swift 4.2
if let song = playlist.randomElement()
print(song)
else
print("Empty playlist.")
3.1.3 洗牌算法
Swift 4.1 不包含任何集合的洗牌算法,所以要采用比较曲折的方式来实现。
// 1
let shuffledPlaylist = playlist.sorted _, _ in arc4random_uniform(2) == 0
// 2
var names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.sort _, _ in arc4random_uniform(2) == 0
Swift 4.2 提供了更加高效更加优雅的实现 Shuffling Algorithms
let shuffledPlaylist = playlist.shuffled()
names.shuffle()
注:使用
shuffled()
来创建一个洗牌后的数组。使用shuffle()
来将数组洗牌。
3.2 动态成员查找
Swift 4.1 使用下面的方式实现自定义下标操作。
/// swift 4.1
class Person
let name: String
let age: Int
private let details: [String: String]
init(name: String, age: Int, details: [String: String])
self.name = name
self.age = age
self.details = details
subscript(key: String) -> String
switch key
case "info":
return "\\(name) is \\(age) years old."
default:
return details[key] ?? ""
let details = ["title": "Author", "instrument": "Guitar"]
let me = Person(name: "Cosmin", age: 32, details: details)
me["info"] // "Cosmin is 32 years old."
me["title"] // "Author"
Swift 4.2 使用动态成员查找来提供点语法来实现下标调用 Dynamic Member Lookup
/// swift 4.2
// 1
@dynamicMemberLookup
class Person
let name: String
let age: Int
private let details: [String: String]
init(name: String, age: Int, details: [String: String])
self.name = name
self.age = age
self.details = details
// 2
subscript(dynamicMember key: String) -> String
switch key
case "info":
return "\\(name) is \\(age) years old."
default:
return details[key] ?? ""
// 3
me.info // "Cosmin is 32 years old."
me.title // "Author"
使用步骤:
- 标记 Person 为 @dynamicMemberLookup 使下标可以使用点语法
- 遵守 @dynamicMemberLookup 实现 subscript(dynamicMember:) 方法
- 使用点语法调用之前定义的下标
注:编译器会在运行时动态评估下标的调用,这样就可以写出像 Python 或者 Ruby 等脚本语言一样类型安全的代码。
动态成员查找不会和类的属性混淆。
me.name // "Cosmin"
me.age // 32
可以使用点语法而非下标来调用 name 和 age。而且派生类可以继承基类的动态成员查找。
@dynamicMemberLookup
class Vehicle
let brand: String
let year: Int
init(brand: String, year: Int)
self.brand = brand
self.year = year
subscript(dynamicMember key: String) -> String
return "\\(brand) made in \\(year)."
class Car: Vehicle
let car = Car(brand: "BMW", year: 2018)
car.info // "BMW made in 2018."
可以通过协议拓展给已有类型添加动态成员查找
// 1
@dynamicMemberLookup
protocol Random
// 2
extension Random
subscript(dynamicMember key: String) -> Int
return Int.random(in: 0..<10)
// 3
extension Int: Random
// 4
let number = 10
let randomDigit = String(number.digit)
let noRandomDigit = String(number).filter String($0) != randomDigit
3.3 枚举实例集合
Swift 4.1 默认没有提供访问枚举实例集合的方式,所以实现方式不是很优雅。
enum Seasons: String
case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
enum SeasonType
case equinox
case solstice
let seasons = [Seasons.spring, .summer, .autumn, .winter]
for (index, season) in seasons.enumerated()
let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
print("\\(season.rawValue) \\(seasonType).")
为了解决这个问题,Swift 4.2 给枚举类型添加了实例数组。
// 1
enum Seasons: String, CaseIterable
case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
enum SeasonType
case equinox
case solstice
// 2
for (index, season) in Seasons.allCases.enumerated()
let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
print("\\(season.rawValue) \\(seasonType).")
如果枚举中包含 unavailable
,需要将available
的case
手动维护协议中的 allCases
。
enum Days: CaseIterable
case monday, tuesday, wednesday, thursday, friday
@available(*, unavailable)
case saturday, sunday
static var allCases: [Days]
return [.monday, .tuesday, .wednesday, .thursday, .friday]
在 allCases
中只能添加 weekdays
,因为 saturday
和 sunday
被标记为各个平台不可用。
枚举实例数组中也可以添加有关联值的实例。
enum BlogPost: CaseIterable
case article
case tutorial(updated: Bool)
static var allCases: [BlogPost]
return [.article, .tutorial(updated: true), .tutorial(updated: false)]
3.4 新的序列方法
Swift 4.1 中的 Sequence 定义了查找指定元素的第一个索引位置或者满足指定条件的第一个元素的方法。
/// swift 4.1
let ages = ["ten", "twelve", "thirteen", "nineteen", "eighteen", "seventeen", "fourteen", "eighteen", "fifteen", "sixteen", "eleven"]
if let firstTeen = ages.first(where: $0.hasSuffix("teen") ),
let firstIndex = ages.index(where: $0.hasSuffix("teen") ),
let firstMajorIndex = ages.index(of: "eighteen")
print("Teenager number \\(firstIndex + 1) is \\(firstTeen) years old.")
print("Teenager number \\(firstMajorIndex + 1) isn't a minor anymore.")
else
print("No teenagers around here.")
Swift 4.2 为了实现一致性重构了方法名
/// swift 4.2
if let firstTeen = ages.first(where: $0.hasSuffix("teen") ),
let firstIndex = ages.firstIndex(where: $0.hasSuffix("teen") ),
let firstMajorIndex = ages.firstIndex(of: "eighteen")
print("Teenager number \\(firstIndex + 1) is \\(firstTeen) years old.")
print("Teenager number \\(firstMajorIndex + 1) isn't a minor anymore.")
else
print("No teenagers around here.")
Swift 4.1 也没有定义查找指定元素的最后一个索引的位置和满足指定条件的的最后一个元素等方法。在 Swift 4.1 中我们可能采用下面的方法来处理。
/// swift 4.1
// 1
let reversedAges = ages.reversed()
// 2
if let lastTeen = reversedAges.first(where: $0.hasSuffix("teen") ),
let lastIndex = reversedAges.index(where: $0.hasSuffix("teen") )?.base,
let lastMajorIndex = reversedAges.index(of: "eighteen")?.base
print("Teenager number \\(lastIndex) is \\(lastTeen) years old.")
print("Teenager number \\(lastMajorIndex) isn't a minor anymore.")
else
print("No teenagers around here.")
Swift 4.2 添加了相应的方法,使用方式如下
if let lastTeen = ages.last(where: $0.hasSuffix("teen") ),
let lastIndex = ages.lastIndex(where: $0.hasSuffix("teen") ),
let lastMajorIndex = ages.lastIndex(of: "eighteen")
print("Teenager number \\(lastIndex + 1) is \\(lastTeen) years old.")
print("Teenager number \\(lastMajorIndex + 1) isn't a minor anymore.")
else
print("No teenagers around here.")
3.5 检测序列元素
Swift 4.1 中没有检查序列中所有元素是否满足某个指定条件的方法。不过你可以实现你自己的方法,例如下面检测集合中的元素是否都是偶数。
let values = [10, 8, 12, 20]
let allEven = !values.contains $0 % 2 == 1
Swift 4.2 添加了新的方法,很好的简化了代码,提升了可读性。
let allEven = values.allSatisfy $0 % 2 == 0
3.6 条件遵守更新
Swift 4.2 给拓展和标准库中添加一些条件遵守方面的改进。
3.6.1 拓展中的条件遵守
Swift 4.1 不能在拓展中自动合成 Equatable 的协议实现。例子如下:
// 1
struct Tutorial : Equatable
let title: String
let author: String
// 2
struct Screencast<Tutorial>
let author: String
let tutorial: Tutorial
// 3
extension Screencast: Equatable where Tutorial: Equatable
// 必须自己实现 == 方法,Swift 4.1 不会自动合成
static func ==(lhs: Screencast, rhs: Screencast) -> Bool
return lhs.author == rhs.author && lhs.tutorial == rhs.tutorial
// 4
let swift41Tutorial = Tutorial(title: "What's New in Swift 4.1?", author: "Cosmin Pupăză")
let swift42Tutorial = Tutorial(title: "What's New In Swift 4.2?", author: "Cosmin Pupăză")
let swift41Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift41Tutorial)
let swift42Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift42Tutorial)
let sameScreencast = swift41Screencast == swift42Screencast
Swift 4.2 只需要遵守协议,不需要实现。因为编译器会添加一个默认的Equatable
协议实现。
extension Screencast: Equatable where Tutorial: Equatable
这个特性也同样支持 Hashable
和 Codable
。
// 1
struct Tutorial: Hashable, Codable
let title: String
let author: String
struct Screencast<Tutorial>
let author: String
let tutorial: Tutorial
// 2
extension Screencast: Hashable where Tutorial: Hashable
extension Screencast: Codable where Tutorial: Codable
// 3
let screencastsSet: Set = [swift41Screencast, swift42Screencast]
let screencastsDictionary = [swift41Screencast: "Swift 4.1", swift42Screencast: "Swift 4.2"]
let screencasts = [swift41Screencast, swift42Screencast]
let encoder = JSONEncoder()
do
try encoder.encode(screencasts)
catch
print("\\(error)")
3.6.2 条件遵守运行时查询
Swift 4.2 实现条件遵守的动态查询。可以从下面的例子看出。
// 1
class Instrument
let brand: String
init(brand: String = "")
self.brand = brand
[译] 那些好玩却尚未被 ECMAScript 2017 采纳的提案
前端周报:W3C 收到 CSS 工作组提案;深入探索 iOS 漏洞利用链;Angular 9 的新特性;