正如Swift官方博客所说,Swift 4.2 是为 Swift 5 的 ABI 稳定性做准备,它包含了一些底层 ABI 的变化。我们看 swift-evolution 中的 proposal 清单,在 4.2 中已经实现了其中一些 proposal。
大纲
- Bool.toggle(
Bool
值反转)
- Sequence and Collection algorithms(新增了序列和集合相关的操作方法)
- Enumerating enum cases(返回枚举属性集合)
- Random numbers(生成随机数)
- Hashable redesign(对
Hashable
的重新设定)
- Conditional conformance enhancements(条件一致性的增强)
- Dynamic member lookup(动态成员查找)
#error and #warning
- MemoryLayout.offset(of:)
- @inlinable
- withUnsafePointer(to::) 和 withUnsafeBytes(of::)
环境要求
1. Bool.toggle
顾名思义,toggle即是Bool新增的一个值反转方法。如果您需要在嵌套数据结构的深处切换布尔值,这尤其有用,因为您不必在赋值的两侧重复相同的表达式。apple官方文档
例如:
struct Layer {
var isHidden = false
}
struct View {
var layer = Layer()
}
var view = View()
// 之前:
view.layer.isHidden = !view.layer.isHidden
view.layer.isHidden // true
// 现在,是不是方便了许多
view.layer.isHidden.toggle() // false
2. Sequence and Collection algorithms (新增了序列和集合相关的算法)
(1). 序列新增allSatisfy
方法
allSatisfy
是为序列新增的算法, 当且仅当序列中的所有元素都满足给定条件时,allSatisfy才返回true。 这个函数通常只用函数式语言调用。apple官方文档
let digits = 0...9
let areAllSmallerThanTen = digits.allSatisfy { $0 < 10 }
areAllSmallerThanTen // 输出:true
let areAllEven = digits.allSatisfy { $0 % 2 == 0 }
areAllEven // 输出:false
(2). 新增last(where:)
,lastIndex(where:)
, and lastIndex(of:)
方法
给序列新增了last(where:)
方法,给集合(字符串)新增了lastIndex(where:)
, and lastIndex(of:)
方法。apple官方文档
let lastEvenDigit = digits.last { $0 % 2 == 0 }
lastEvenDigit // 输出:8
let text = "Vamos a la playa"
let lastWordBreak = text.lastIndex(where: { $0 == " " })
let lastWord = lastWordBreak.map { text[text.index(after: $0)...] }
lastWord // 输出:playa
text.lastIndex(of: " ") == lastWordBreak // true
(3)、把 index(of:)
和 index(where:)
更名为: firstIndex(of:)
和 firstIndex(where:)
let firstWordBreak = text.firstIndex(where: { $0 == " " })
let firstWord = firstWordBreak.map { text[..<$0] }
firstWord // 输出:Vamos
3. Enumerating enum cases
enum新增了返回集合属性的方法:编译器可以自动为枚举生成allCases
属性,为您提供始终最新的枚举列表。 您所要做的就是使您的枚举符合新的CaseIterable
协议。
enum Terrain: CaseIterable {
case water
case forest
case desert
case road
}
Terrain.allCases // 输出:water, forest, desert, road
Terrain.allCases.count // 输出:4
注意,自动合成仅适用于没有关联值的枚举。 因为关联值意味着枚举可能具有可能无限数量的可能值。
如果所有可能值的列表都是有限的,您可以手动实现协议。 举个例子:这里都是遵守了CaseIterable协议条件一致的可选类型的值。apple官方文档
extension Optional: CaseIterable where Wrapped: CaseIterable {
public typealias AllCases = [Wrapped?]
public static var allCases: AllCases {
return Wrapped.allCases.map { $0 } + [nil]
}
}
// 注意:这不是可选绑定
Terrain?.allCases // 输出:water, forest, desert, road,nil
Terrain?.allCases.count // 输出:5
4. Random numbers
使用随机数在Swift中一直不是很友好,因为(a)你必须直接调用C语言的API来实现,(b)没有一个好的跨平台的生产随机数的API。
(1)、生成随机数
在Swift4.2中,苹果将随机数生成添加到标准库中。Apple官方文档
支持所有数字类型的生成随机数方法random(in:)
,它返回给定范围内的随机数(默认情况下均匀分布):
Int.random(in: 1...1000) // 输出:432
UInt8.random(in: .min ... .max) // 输出:226
Double.random(in: 0..<1) // 输出:0.9219874372193483
同时,这个api也很好的避免了modulo bias偏差的发生。
func coinToss(count tossCount: Int) -> (heads: Int, tails: Int) {
var tally = (heads: 0, tails: 0)
for _ in 0..<tossCount {
let isHeads = Bool.random()
if isHeads {
tally.heads += 1
} else {
tally.tails += 1
}
}
return tally
}
let (heads, tails) = coinToss(count: 100)
print("100 coin tosses — heads: \(heads), tails: \(tails)") // 输出:100 coin tosses — heads: 44, tails: 56
(2)、返回集合(如:字符串)的随机值
如果一个集合使用randomElement
方法,且集合为空,则返回的值为nil
。
let emotions = "😀😂😊😍🤪😎😩😭😡"
let randomEmotion = emotions.randomElement()! // 输出:😀
(3)、shuffle
打乱集合内顺序的方法
适用于MutableCollection
和RandomAccessCollection
的所有类型:
let numbers = 1...10
let shuffled = numbers.shuffled() // 输出:[7, 10, 5, 4, 8, 2, 9, 3, 6, 1]
var mutableNumbers = Array(numbers) // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
mutableNumbers.shuffle() // 输出:[9, 4, 5, 10, 1, 6, 7, 2, 3, 8]
(4)、自定义随机数生成器
标准库附带一个默认的随机数生成器Random.default
,对于大多数简单的用例来说,这可能是一个不错的选择。
如果您有特殊要求,可以采用RandomNumberGenerator
协议实现自己的随机数生成器。 用于生成随机值的所有API都提供了一个允许用户传入其首选随机数生成器的重载:
/// A dummy random number generator that just mimics `Random.default`.
struct MyRandomNumberGenerator: RandomNumberGenerator {
var base = Random.default
mutating func next() -> UInt64 {
return base.next()
}
}
var customRNG = MyRandomNumberGenerator()
Int.random(in: 0...100, using: &customRNG) // 输出:4
(5)、扩展您自己的类型
最强大的是您可以按照相同的模式,为您自己的类型提供随机数据API👍。
enum Suit: String, CaseIterable {
case diamonds = "♦"
case clubs = "♣"
case hearts = "♥"
case spades = "♠"
static func random<T: RandomNumberGenerator>(using generator: inout T) -> Suit {
// Using CaseIterable for the implementation
return allCases.randomElement(using: &generator)!
}
static func random() -> Suit {
return Suit.random(using: &Random.default)
}
}
let randomSuit = Suit.random()
randomSuit.rawValue // 输出:♣
5. 重新设计Hashable
Swift 4.1 SE-0185 中引入的编译器合成的Equatable
和Hashable
一致性大大减少了您必须编写的手动Hashable实现的数量。
但是,如果您需要自定义类型的Hashable
一致性,Hashable
协议 SE-0206 的重新设计使这项任务变得更加容易。
在新的Hashable
世界中,您现在必须实现hash(into :)
方法,而不是实现hashValue
。此方法提供了一个Hasher
对象,您在实现中所要做的就是通过重复调用hasher.combine(_ :)
将其包含在您希望包含在哈希值中的值。
与旧方法相比,优势在于您不必提出自己的算法来组合您的类型组成的哈希值。标准库(以Hasher
形式)提供的哈希函数几乎肯定比我们大多数人编写的更好,更安全。
作为示例,这里是具有一个存储属性的类型,其充当用于昂贵计算的高速缓存。我们应该在Equatable
和Hashable
实现中忽略distanceFromOrigin
的值:
struct Point {
var x: Int { didSet { recomputeDistance() } }
var y: Int { didSet { recomputeDistance() } }
/// Cached. Should be ignored by Equatable and Hashable.
private(set) var distanceFromOrigin: Double
init(x: Int, y: Int) {
self.x = x
self.y = y
self.distanceFromOrigin = Point.distanceFromOrigin(x: x, y: y)
}
private mutating func recomputeDistance() {
distanceFromOrigin = Point.distanceFromOrigin(x: x, y: y)
}
private static func distanceFromOrigin(x: Int, y: Int) -> Double {
return Double(x * x + y * y).squareRoot()
}
}
extension Point: Equatable {
static func ==(lhs: Point, rhs: Point) -> Bool {
// Ignore distanceFromOrigin for determining equality
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
在我们的hash(into :)
实现中,我们需要做的就是将相关属性提供给hasher
。
这比提供自己的哈希组合功能更容易(也更有效)。 例如,hashValue
的一个简单实现可能会对两个坐标进行异或运算:return x ^ y
。 这将是效率较低的散列函数,因为Point(3, 4)
和Point(4, 3)
将以相同的散列值结束。
extension Point: Hashable {
func hash(into hasher: inout Hasher) {
// Ignore distanceFromOrigin for hashing
hasher.combine(x)
hasher.combine(y)
}
}
let p1 = Point(x: 3, y: 4)
p1.hashValue
let p2 = Point(x: 4, y: 3)
p2.hashValue
assert(p1.hashValue != p2.hashValue)
6. Conditional conformance enhancements(条件一致性的增强)
(1). 条件协议一致性
条件协议一致性SE-0143是Swift 4.1的主要特征。提议的最后一部分,运行时查询条件一致性,已经在SWIFT 4.2中着陆。这意味着对协议类型的动态强制转换(使用is还是as?)当条件满足条件时,该值有条件地符合协议,现在将成功。
func isEncodable(_ value: Any) -> Bool {
return value is Encodable
}
// Swift 4.1之前都返回false,不支持深入到数组里面去判断
let encodableArray = [1, 2, 3]
isEncodable(encodableArray) // true
// Verify that the dynamic check doesn't succeed when the conditional conformance criteria aren't met.
struct NonEncodable {}
let nonEncodableArray = [NonEncodable(), NonEncodable()]
assert(isEncodable(nonEncodableArray) == false)
(2). 扩展中的合成一致性
对编译器合成协议一致性的一个小的但重要的改进,例如SE-0185中介绍Equatable
和Hashable
的 一致性。
协议一致性现在可以在扩展中合成,而不仅仅是在类型定义上(扩展必须仍然在与类型定义相同的文件中)。这不仅仅是外观上的改变,因为它允许自动合成条件一致性,使其具有Equatable
(可等价性)、Hashable
(可哈希性)、Encodable
(可编码性) 和Decodable
(可解码性)。
这个例子来自WWDC 2018 Swift会议中的新内容。我们可以有条件地符合Equatable
和Hashable
:
enum Either<Left, Right> {
case left(Left)
case right(Right)
}
// No code necessary
extension Either: Equatable where Left: Equatable, Right: Equatable {}
extension Either: Hashable where Left: Hashable, Right: Hashable {}
Either<Int, String>.left(42) == Either<Int, String>.left(42)
7. Dynamic member lookup(动态成员查找)
SE-0195为类型声明引入了@dynamicMemberLookup
属性。
可以使用任何属性样式访问器(使用点标记)调用@dynamicMemberLookup
类型的变量。编译器不会检查给定名称的成员是否存在。相反,编译器将这样的访问转换为下标访问器的调用,这些访问器将成员名称作为字符串传递。
此功能的目标是促进快速语言和动态语言如Python
之间的互操作性。谷歌的Swift
的TunSoFrand团队,推动了这个提议,实现了一个Python
桥接库,可以从Swift
调用Python
代码。Pedro José Pereira Vieito将这些打包成SwiftPM
包,并命名为PythonKit。
SE-0195不需要启用这种互操作性,但它使所得的Swift
语法更加完善。值得注意的是,SE-0195只处理属性样式成员查找(即简单的吸引子和没有参数的设置)。动态方法调用语法的第二个“动态可调用”建议仍在进行中。
虽然Python一直是参与该提议的人的主要焦点,但与Ruby或JavaScript等其他动态语言互操作层也可以利用它。
也不局限于这一个用例。任何当前具有基于字符串的下标样式API的类型都可以转换为动态成员查找样式。SE-0195显示了一个JSON类型,在这里您可以使用点标记来钻入嵌套字典中。
下面是Doug Gregor的另一个例子:一个Environment
类型,它为您提供了对过程环境变量的属性样式访问。注意突变也起作用。
import Darwin
/// The current process's environment.
///
/// - Author: Doug Gregor, https://gist.github.com/DougGregor/68259dd47d9711b27cbbfde3e89604e8
@dynamicMemberLookup
struct Environment {
subscript(dynamicMember name: String) -> String? {
get {
guard let value = getenv(name) else { return nil }
return String(validatingUTF8: value)
}
nonmutating set {
if let value = newValue {
setenv(name, value, /*overwrite:*/ 1)
} else {
unsetenv(name)
}
}
}
}
let environment = Environment()
environment.USER // 输出:"lisilong"
environment.HOME // 输出:"/Users/lisilong"
environment.PATH // 输出:"/usr/bin:/bin:/usr/sbin:/sbin"
// Mutations are allowed if the subscript has a setter
environment.MY_VAR = "Hello world" // 输出:"Hello world"
environment.MY_VAR // 输出:"Hello world"
8. #error 和 #warning 提示和警告的使用
SE-0196官方文档中介绍了#error
和#warning
的一些使用场景和用法。
这里举例说明,比如我们需要醒目提示的时候,可以用#warning
来提示TODO事项,如下:
func doSomethingImportant() {
#warning("TODO: missing implementation")
}
doSomethingImportant()
#error
可以很好地抛出类似环境不兼容等致命性的错误提示,例如:
#if canImport(UIKit)
// ...
#elseif canImport(AppKit)
// ...
#else
#error("This playground requires UIKit or AppKit")
#endif
9. MemoryLayout.offset(of:)
SE-0210新增了一个设置偏移量的方法offset(of:)
在MemoryLayout
类型中,补充现有API以获得类型的size
(大小)、stride
(步幅)和alignment
(对齐方式)。
offset(of:)
通过键值路径的方式存储属性,并返回属性存储的偏移量。一个很有用的场景是,将复杂的数组值传递给图形API。
struct Point {
var x: Float
var y: Float
var z: Float
}
MemoryLayout<Point>.offset(of: \Point.z)
10. @inlinable
SE-0193引入了两个新的属性:@inlinable
和 @usableFromInline
。
这些对于应用程序代码来说是不必须的,开发者可以将一些公共功能注释为@inlinable
。这给编译器提供了优化跨模块边界的泛型代码的选项。
例如,提供一组集合算法的库可以将这些方法标记为@inlinable
,以允许编译器将使用这些算法的客户端代码专门化为在构建库时未知的类型。
示例(采用SE-0193给出的示例):
// Inside CollectionAlgorithms module:
extension Sequence where Element: Equatable {
/// Returns `true` if all elements in the sequence are equal.
@inlinable
public func allEqual() -> Bool {
var iterator = makeIterator()
guard let first = iterator.next() else {
return true
}
while let next = iterator.next() {
if first != next {
return false
}
}
return true
}
}
[1,1,1,1,1].allEqual() // 输出:true
Array(repeating: 42, count: 1000).allEqual()
[1,1,2,1,1].allEqual() // 输出:false
在使函数不能链接之前仔细考虑。使用@inlinable
有效地使库的公共接口的功能部分成为主体。如果您稍后更改实现(例如修复错误),则针对旧版本编译的二进制文件可能继续使用旧的(内联)代码,甚至是旧的和新的混合(因为@inlinable
仅是提示;优化器决定每个调用站点是否要内嵌代码)。
因为可以将可链接函数发送到客户端二进制,所以不允许引用客户端二进制文件不可见的声明。您可以使用@usableFromInline
在你的“ABI-public”库中进行某些内部声明,允许它们在可链接函数中使用。
11. withUnsafePointer(to::) 和 withUnsafeBytes(of::)
在不可变的值调用withUnsafePointer(to:_:)
和 withUnsafeBytes(of:_:)
方法时,需要注意的是,它们需要参数是可变值,因为参数是inout
。
SE-0205增加了与不可变值一起使用的重载。
let x: UInt16 = 0xabcd
let (firstByte, secondByte) = withUnsafeBytes(of: x) { ptr in
(ptr[0], ptr[1])
}
String(firstByte, radix: 16) // 输出:cd
String(secondByte, radix: 16) // 输出:ab
迁移到Swift 4.2的问题:
(1). Error compilation "While deserializing SIL global "UIEdgeInsetsZero"
Xcode 10.0 beta, Swift编译器的错误,请参阅https://bugs.swift.org/browse/SR-7879。
解决方案:
// 处理Xcode 10.0 Swift 4.2 编译器错误:https://bugs.swift.org/browse/SR-7879
#if swift(>=4.2)
import UIKit.UIGeometry
extension UIEdgeInsets {
public static let zero = UIEdgeInsets()
}
#endif
持续更新中...
更多Swift版本的相关更新,可以查阅作者的其它文章:
iOS开发——Swift 4.0 + Xcode9 适配小结
感谢:
Swift的官方博客
Ole的whats-new-in-swift-4-2
如果本文对您有所帮助,请不要吝啬您的点赞❤️和喜欢❤️哦!!