Swift 4.1及4.2的最新更新看这里

96
青苹果园
0.7 2018.07.05 18:44* 字数 2873

正如Swift官方博客所说,Swift 4.2 是为 Swift 5 的 ABI 稳定性做准备,它包含了一些底层 ABI 的变化。我们看 swift-evolution 中的 proposal 清单,在 4.2 中已经实现了其中一些 proposal。

大纲

    1. Bool.toggle(Bool值反转)
    1. Sequence and Collection algorithms(新增了序列和集合相关的操作方法)
    1. Enumerating enum cases(返回枚举属性集合)
    1. Random numbers(生成随机数)
    1. Hashable redesign(对Hashable的重新设定)
    1. Conditional conformance enhancements(条件一致性的增强)
    1. Dynamic member lookup(动态成员查找)
    1. #error and #warning
    1. MemoryLayout.offset(of:)
    1. @inlinable
    1. withUnsafePointer(to::) 和 withUnsafeBytes(of::)

环境要求

Xcode 10 beta

或者Swift 4.2

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打乱集合内顺序的方法

适用于MutableCollectionRandomAccessCollection的所有类型:

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 中引入的编译器合成的EquatableHashable一致性大大减少了您必须编写的手动Hashable实现的数量。

但是,如果您需要自定义类型的Hashable一致性,Hashable协议 SE-0206 的重新设计使这项任务变得更加容易。

在新的Hashable世界中,您现在必须实现hash(into :)方法,而不是实现hashValue。此方法提供了一个Hasher对象,您在实现中所要做的就是通过重复调用hasher.combine(_ :)将其包含在您希望包含在哈希值中的值。

与旧方法相比,优势在于您不必提出自己的算法来组合您的类型组成的哈希值。标准库(以Hasher形式)提供的哈希函数几乎肯定比我们大多数人编写的更好,更安全。

作为示例,这里是具有一个存储属性的类型,其充当用于昂贵计算的高速缓存。我们应该在EquatableHashable实现中忽略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中介绍EquatableHashable的 一致性。

协议一致性现在可以在扩展中合成,而不仅仅是在类型定义上(扩展必须仍然在与类型定义相同的文件中)。这不仅仅是外观上的改变,因为它允许自动合成条件一致性,使其具有Equatable(可等价性)、Hashable(可哈希性)、Encodable(可编码性) 和Decodable(可解码性)。

这个例子来自WWDC 2018 Swift会议中的新内容。我们可以有条件地符合EquatableHashable

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之间的互操作性。谷歌的SwiftTunSoFrand团队,推动了这个提议,实现了一个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

如果本文对您有所帮助,请不要吝啬您的点赞❤️和喜欢❤️哦!!

iOS开发
Gupao