返回顶部
首页 > 资讯 > 精选 >Swift 5.9 有哪些新特性(二)
  • 460
分享到

Swift 5.9 有哪些新特性(二)

swift蓝桥杯开发语言原力计划 2023-08-17 11:08:39 460人浏览 泡泡鱼
摘要

文章目录 前言Noncopyable 结构体和枚举结束变量绑定的生命周期makeStream() 方法添加 sleep(for:) 到 ClockDiscarding task groups总结 前言 虽然 Swift

在这里插入图片描述


在这里插入图片描述

前言

虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏、非可复制类型、自定义 actor 执行器等等都将在 Swift 5.9 中推出,再次带来了一个巨大的更新。

在本文中,将介绍这个版本中最重要的变化,提供代码示例和解释,以便可以自行尝试。需要在 Xcode 14 中安装最新的 Swift 5.9 工具链,或者使用 Xcode 15 beta。

Noncopyable 结构体和枚举

SE-0390 引入了无法复制的结构体和枚举的概念,从而允许在代码的多个位置共享一个结构体或枚举的单个实例,虽然只有一个所有者,但现在可以在代码的不同部分访问。

首先,此更改引入了用于取消要求的新语法:~Copyable。这意味着 “此类型不能被复制”,并且此取消语法目前在其他地方不可用 - 例如,我们不能使用 ~Equatable 来退出类型的 ==

因此,我们可以像下面代码创建一个新的不可复制的 User 结构体:

struct User: ~Copyable {    var name: String}

注意:Noncopyable 不能满足除 Sendable 之外的任何协议。

一旦创建了 User 实例,其不可复制的特性意味着它与 Swift 的先前版本不一样。例如,下面的示例代码:

func createUser() {    let newUser = User(name: "Anonymous")    var userCopy = newUser    print(userCopy.name)}createUser()

但是我们已经声明了 User 结构体为不可复制,也无法复制 newUser,将 newUser 分配给 userCopy 导致原始的 newUser 值被消耗,这意味着不能使用,因为所有权现在属于 userCopy。如果尝试将 print(userCopy.name) 更改为 print(newUser.name),Swift 会抛出一个编译器错误。

新的限制还适用于如何将非可复制类型用作函数参数:SE-0377 规定函数必须明确指定是打算消费值并在函数完成后使其在调用点无效,还是希望借用值以便与代码中的其他借用部分同时读取其数据。

因此,可以编写一个函数来创建用户,另一个函数来借用用户以获得只读访问其数据的权限:

func createAndGreetUser() {    let newUser = User(name: "Anonymous")    greet(newUser)    print("Goodbye, \(newUser.name)")}func greet(_ user: borrowing User) {    print("Hello, \(user.name)!")}createAndGreetUser()

与此相反,如果我们使 greet() 函数使用 consuming User,则 print("Goodbye, \(newUser.name)") 将不被允许 - Swift 将认为 greet() 运行后,newUser 值将无效。另一方面,由于 consuming 方法必须结束对象的生命周期,可以自由地修改其属性。

这种共享行为赋予了非可复制结构体以前仅限于类和 actor 的超能力:当对非可复制实例的最后一个引用被销毁时,可以提供自动运行的析构函数。

重要提示: 这与类上的析构函数的行为略有不同,可能是早期实现的问题或有意为之。

首先,下面是使用类的析构函数的代码示例:

class Movie {    var name: String    init(name: String) {        self.name = name    }    deinit {        print("\(name) is no longer available")    }}func watchMovie() {    let movie = Movie(name: "The Hunt for Red October")    print("Watching \(movie.name)")}watchMovie()

当运行该代码时,会先打印 “Watching The Hunt for Red October”,然后打印 “The Hunt for Red October is no longer available”。但是,如果将类型的定义从 class Movie 更改为 struct Movie: ~Copyable,将会看到这两个 print() 语句以相反的顺序运行 - 先说电影不再可用,然后说正在观看。

非可复制类型内部的方法默认情况下是借用的,但是可以像可复制类型一样标记为 mutating,并且还可以标记为 consuming,表示该值在方法运行后无效。

例如,我们熟悉的电影和电视剧《碟中谍》,秘密特工们通过一卷只能播放一次的自毁磁带获得任务指令。对于这样的方式,非可复制结构体非常适合:

struct MissionImpossibleMessage: ~Copyable {    private var message: String    init(message: String) {        self.message = message    }    consuming func read() {        print(message)    }

这样标记的 message 本身是私有的,因此只能通过调用消费实例的 read() 方法来访问它。

与变异方法不同,消费方法可以在类型的常量实例上运行。因此,像下面这样的代码是可以的:

func createMessage() {    let message = MissionImpossibleMessage(message: "You need to abseil down a skyscraper for some reason.")    message.read()}createMessage()

注意: 因为 message.read() 消费了 message 实例,所以尝试第二次调用 message.read() 将会报错。

与析构函数结合使用时,消费方法会使清理工作重复执行。例如,如果在游戏中跟踪高分,可能希望具有一个消费的 finalize() 方法,将最新的高分写入永久存储,并阻止其他人进一步更改分数,但在对象销毁时也保存最新的分数到磁盘。

为了避免这个问题,Swift 5.9 引入了一个新的 discard 操作符,可以用于非可复制类型的消费方法。在消费方法中使用 discard self 可以阻止该对象的析构函数运行。

因此,可以像这样实现 HighScore 结构:

struct HighScore: ~Copyable {    var value = 0    consuming func finalize() {        print("Saving score to disk…")        discard self    }    deinit {        print("Deinit is saving score to disk…")    }}func createHighScore() {    var highScore = HighScore()    highScore.value = 20    highScore.finalize()}createHighScore()

提示: 当运行该代码时,你会看到 deinitializer 消息被打印两次 - 一次是在更改 value 属性时,实际上销毁并重新创建了结构体,一次是在 createHighScore() 方法结束时。

在使用这个新功能时,还有一些额外的复杂性需要注意:

  • 类和 actor 不能是非可复制的。
  • 非可复制类型暂时不支持泛型,这排除了可选的非可复制对象和非可复制对象数组
  • 如果在另一个结构体或枚举类型中将非可复制类型用作属性,那么父结构体或枚举类型也必须是非可复制的。
  • 当对现有类型添加或移除 Copyable 时需要非常小心,因为会改变用法。如果在库中发布代码,这将破坏 ABI。

结束变量绑定的生命周期

使用消耗运算符结束变量绑定的生命周期

SE-0366 扩展了对可复制类型的局部变量和常量的消耗值概念,这对于希望避免在其数据传递过程中发生不必要的保留/释放调用的开发人员可能很有益处。

最简单的形式下,消耗运算符如下所示:

struct User {    var name: String}func createUser() {    let newUser = User(name: "Anonymous")    let userCopy = consume newUser    print(userCopy.name)}createUser()

其中重要的是 let userCopy 这一行,同时执行两个操作:

  1. newUser 的值复制到 userCopy 中。
  2. 结束 newUser 的生命周期,因此任何进一步访问它的尝试都会引发错误。

这样可以明确告诉编译器“不允许再次使用这个值”,这将代表强制执行这个规则。

可以看到这在使用所谓的黑洞 _ 时特别常见,我们不希望复制数据,而只是想将其标记为已销毁,例如:

func consumeUser() {    let newUser = User(name: "Anonymous")    _ = consume newUser}

实际上,最常见的情况可能是将值传递给如下的函数:

func createAndProcessUser() {    let newUser = User(name: "Anonymous")    process(user: consume newUser)}func process(user: User) {    print("Processing \(name)…")}createAndProcessUser()

有两件特别值得了解的事情。

首先,Swift 跟踪代码的哪些分支消耗了值,并有条件地强制执行规则。因此,在这段代码中,两种可能性中只有一种消耗了 User 实例:

func greetRandomly() {    let user = User(name: "Taylor Swift")    if Bool.random() {        let userCopy = consume user        print("Hello, \(userCopy.name)")    } else {        print("Greetings, \(user.name)")    }}greetRandomly()

其次,严格来说,consume 操作符作用于绑定而不是值。实践中,这意味着如果使用一个变量进行消耗,可以重新初始化该变量并正常使用:

func createThenRecreate() {    var user = User(name: "Roy Kent")    _ = consume user    user = User(name: "Jamie Tartt")    print(user.name)}createThenRecreate()

makeStream() 方法

SE-0388 在 AsyncStreamAsyncThrowingStream 中添加了一个新的 makeStream() 方法,返回流本身以及其 continuation。

因此,不再需要编写以下代码:

var continuation: AsyncStream.Continuation!let stream = AsyncStream { continuation = $0 }

现在可以同时获取:

let (stream, continuation) = AsyncStream.makeStream(of: String.self)

这在需要在当前上下文之外访问 continuation 的地方特别方便,例如在另一个方法中。例如,以前可能会像下面这样编写一个简单的数字生成器,需要将 continuation 存储为自己的属性,以便能够从 queueWork() 方法中调用:

struct OldNumberGenerator {    private var continuation: AsyncStream.Continuation!    var stream: AsyncStream!    init() {        stream = AsyncStream(Int.self) { continuation in            self.continuation = continuation        }    }    func queueWork() {        Task {            for i in 1...10 {                try await Task.sleep(for: .seconds(1))                continuation.yield(i)            }            continuation.finish()        }    }}

使用新的 makeStream(of:) 方法,这段代码变得简单多了:

struct NewNumberGenerator {    let (stream, continuation) = AsyncStream.makeStream(of: Int.self)    func queueWork() {        Task {            for i in 1...10 {                try await Task.sleep(for: .seconds(1))                continuation.yield(i)            }            continuation.finish()        }    }}

添加 sleep(for:) 到 Clock

SE-0374 在 Swift 的 Clock 协议中添加了一个新的扩展方法,允许暂停执行一段时间,同时还支持特定容差的基于持续时间的任务睡眠。

Clock 的更改虽然很小,但非常重要,特别是在模拟具体 Clock 实例以消除在测试中存在于生产环境中的延迟时。

例如,可以使用任何类型的 Clock 创建这个类,并在触发保存操作之前使用该 Clock 进行睡眠:

class DataController: ObservableObject {    var clock: any Clock    init(clock: any Clock) {        self.clock = clock    }    func delayedSave() async throws {        try await clock.sleep(for: .seconds(1))        print("Saving…")    }}

由于使用了 any Clock,因此在生产中可以使用 ContinuousClock,而在测试中可以使用自定义的 DummyClock,其中忽略所有的 sleep() 命令以使测试运行快速。

在较旧的 Swift 版本中,相应的代码理论上可能是 try await clock.sleep(until: clock.now.advanced(by: .seconds(1))),但在这个示例中不起作用,因为 Swift 不知道具体使用了哪种类型的时钟,因此无法获得 clock.now

至于对于 Task 睡眠的改变,可以从以下代码:

try await Task.sleep(until: .now + .seconds(1), tolerance: .seconds(0.5))

简化为:

try await Task.sleep(for: .seconds(1), tolerance: .seconds(0.5))

Discarding task groups

SE-0381 添加了新的 Discarding task groups,填补了当前 api 中的一个重要空白:在任务组内部创建的任务在完成后会自动丢弃和销毁,这意味着长时间运行的任务组(或者在 WEB 服务器等情况下可能一直运行的任务组)不会随着时间的推移泄漏内存。

在使用原始的 withTaskGroup() API 时,可能会遇到问题,因为 Swift 只在调用 next() 或循环遍历任务组的子任务时才丢弃子任务及其结果数据。调用 next() 会导致代码在所有子任务都在执行时暂停,因此面临的问题是:希望服务器始终监听连接以便添加任务来处理,但是还需要定期停止以清理已完成的旧任务。

在 Swift 5.9 中引入了解决这个问题的清晰方案,添加了 withDiscardingTaskGroup()withThrowingDiscardingTaskGroup() 函数,用于创建新的丢弃式任务组。这些任务组会自动在每个任务完成后丢弃和销毁任务,无需手动调用 next() 来消费它。

为了了解触发问题的情况,可以实现一个简单的目录监视器,循环运行并报告已添加或删除的文件或目录的名称:

struct FileWatcher {    // 正在监视文件更改的 URL。    let url: URL    // 已返回的 URL 集合。    private var handled = Set()    init(url: URL) {        self.url = url    }    mutating func next() async throws -> URL? {        while true {            // 读取我们目录的最新内容,或者如果发生问题则退出。            guard let contents = try? FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil) else {                return nil            }            // 找出我们尚未处理的 URL。            let unhandled = handled.symmetricDifference(contents)            if let newURL = unhandled.first {                // 如果我们已经处理过此 URL,则它必须已被删除。                if handled.contains(newURL) {                    handled.remove(newURL)                } else {                    // 否则,此 URL 是新的,因此将其标记为已处理。                    handled.insert(newURL)                    return newURL                }            } else {                // 没有文件差异;睡眠几秒钟后重试。                try await Task.sleep(for: .microseconds(1000))            }        }    }}

然后可以从一个简单的应用程序中使用,尽管出于简洁起见,只打印 URL 而不进行任何复杂的处理:

struct FileProcessor {    static func main() async throws {        var watcher = FileWatcher(url: URL(filePath: "/Users/twostraws"))        try await withThrowingTaskGroup(of: Void.self) { group in            while let newURL = try await watcher.next() {                group.addTask {                    process(newURL)                }            }        }    }    static func process(_ url: URL) {        print("Processing \(url.path())")    }}

这段代码将永远运行,或者至少直到用户终止程序或监视的目录不再可访问为止。然而,由于使用了 withThrowingDiscardingTaskGroup(),这个问题就不存在了:每次调用 addTask() 时都会创建一个新的子任务,但由于没有在任何地方调用 group.next(),这些子任务永远不会被销毁。每次可能只增加几百字节,这段代码将占用越来越多的内存,直到最终操作系统耗尽内存并被迫终止程序。

这个问题在 Discarding task groups 中完全消失:只需将 withThrowingTaskGroup(of: Void.self) 替换为 withThrowingDiscardingTaskGroup,每个子任务在完成工作后将自动销毁。

总结

特别感谢 Swift社区 编辑部的每一位编辑,感谢大家的辛苦付出,为 Swift社区 提供优质内容,为 Swift 语言的发展贡献自己的力量。

来源地址:https://blog.csdn.net/qq_36478920/article/details/131450564

--结束END--

本文标题: Swift 5.9 有哪些新特性(二)

本文链接: https://lsjlt.com/news/372682.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Swift 5.9 有哪些新特性(二)
    文章目录 前言Noncopyable 结构体和枚举结束变量绑定的生命周期makeStream() 方法添加 sleep(for:) 到 ClockDiscarding task groups总结 前言 虽然 Swift...
    99+
    2023-08-17
    swift 蓝桥杯 开发语言 原力计划
  • Swift 5.9 有哪些新特性(一)
    文章目录 前言if 和 switch 表达式Value 和 Type 参数包 前言 虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏、非可复制类型、自定...
    99+
    2023-08-16
    swift 开发语言 ios 原力计划
  • Swift 5.9 Macros 有哪些新更新
    文章目录 前言Macros(宏)需要了解的关键信息环境准备创建一个宏定义宏实际使用宏 总结 前言 虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏...
    99+
    2023-08-16
    swift 开发语言 蓝桥杯 ios swiftui 原力计划
  • Swift鲜为人知的特性有哪些
    这篇文章主要讲解了“Swift鲜为人知的特性有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Swift鲜为人知的特性有哪些”吧!考虑这样一种常见情况:在没有遇到任何错误的情况下,要启动网...
    99+
    2023-06-16
  • MySQL8.0新特性有哪些
    这篇文章给大家分享的是有关MySQL8.0新特性有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。1、 默认字符集由latin1变为utf8mb4在8.0版本之前,默认字符集为...
    99+
    2024-04-02
  • oracle18c新特性有哪些
    oracle18c新特性有哪些,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Oracle宣布Database...
    99+
    2024-04-02
  • Vue3有哪些新特性
    本篇内容介绍了“Vue3有哪些新特性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!组件v-model支持参...
    99+
    2024-04-02
  • PostgreSQL11有哪些新特性
    这篇文章主要介绍“PostgreSQL11有哪些新特性”,在日常操作中,相信很多人在PostgreSQL11有哪些新特性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Post...
    99+
    2024-04-02
  • Oracle12C新特性有哪些
    这篇文章主要介绍“Oracle12C新特性有哪些”,在日常操作中,相信很多人在Oracle12C新特性有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Oracle12C新...
    99+
    2024-04-02
  • jQuery1.6.4有哪些新特性
    本篇内容介绍了“jQuery1.6.4有哪些新特性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Data:...
    99+
    2024-04-02
  • MySQL5.6有哪些新特性
    这篇文章主要介绍“MySQL5.6有哪些新特性”,在日常操作中,相信很多人在MySQL5.6有哪些新特性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MySQL5.6有哪些新...
    99+
    2024-04-02
  • Swagger3.0有哪些新特性
    这篇文章主要讲解了“Swagger3.0有哪些新特性”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Swagger3.0有哪些新特性”吧!支持 OpenAPI...
    99+
    2024-04-02
  • css3新特性有哪些
    这篇文章将为大家详细讲解有关css3新特性有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。   CSS3的新特征有:1、圆角效果;2、图形化边界;3、块阴影与文字阴...
    99+
    2024-04-02
  • PHP8.1新特性有哪些
    这篇文章主要为大家展示了“PHP8.1新特性有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“PHP8.1新特性有哪些”这篇文章吧。当大多数人的 Linux ...
    99+
    2024-04-02
  • ES10新特性有哪些
    这篇文章给大家分享的是有关ES10新特性有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。ES10 虽然没有像 ES6 那么多新特性,但 ES10 仍然有一些有用的特性。文本通...
    99+
    2024-04-02
  • JavaScript新特性有哪些
    这篇文章主要介绍“JavaScript新特性有哪些”,在日常操作中,相信很多人在JavaScript新特性有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JavaScri...
    99+
    2024-04-02
  • MindSpore有哪些新特性
    这篇文章主要讲解了“MindSpore有哪些新特性”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MindSpore有哪些新特性”吧!一、效率提升大幅提升动态...
    99+
    2024-04-02
  • CSS3有哪些新特性
    这篇文章主要介绍“CSS3有哪些新特性”,在日常操作中,相信很多人在CSS3有哪些新特性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”CSS3有哪些新特性”的疑惑有所帮助!接...
    99+
    2024-04-02
  • Java9新特性有哪些
    本篇内容介绍了“Java9新特性有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!新的APIs和性能特性:轻量级的JSON APIHTTP...
    99+
    2023-06-17
  • JMS新特性有哪些
    这篇文章主要为大家展示了“JMS新特性有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JMS新特性有哪些”这篇文章吧。JMS(Java Message Service,Java消息服务)是J...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作