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

正如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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,732评论 2 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • 《大学记忆之蕾蕾》写出之后,在大学同学群里激起了很大的反响:首先黄大仙不无担心地说怕蕾蕾平复的内心又因此而生波澜,...
    活着不易阅读 266评论 0 1
  • 每天坐在办公室里,屁股下有凳子,脑袋上有帽子。凳子实实在在,看得见摸得着,踏踏实实履行支撑的职责;帽子飘...
    蝈蝈2018阅读 1,627评论 78 41
  • 上一章 目录戳这里这里 “嗯。”林玲笑着说:“我现在要朝着理想好好努力了,人不能...
    马小乔Lov244阅读 268评论 0 1