我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Swift 5.9 有哪些新特性(二)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Swift 5.9 有哪些新特性(二)

在这里插入图片描述


在这里插入图片描述

前言

虽然 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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Swift 5.9 有哪些新特性(二)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Swift 5.9 有哪些新特性(二)

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

Swift 5.9 有哪些新特性(一)

文章目录 前言if 和 switch 表达式Value 和 Type 参数包 前言 虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏、非可复制类型、自定
2023-08-16

Swift 5.9 Macros 有哪些新更新

文章目录 前言Macros(宏)需要了解的关键信息环境准备创建一个宏定义宏实际使用宏 总结 前言 虽然 Swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏
2023-08-16

Swift鲜为人知的特性有哪些

这篇文章主要讲解了“Swift鲜为人知的特性有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Swift鲜为人知的特性有哪些”吧!考虑这样一种常见情况:在没有遇到任何错误的情况下,要启动网
2023-06-16

html5有哪些新特性

html5的新特性有:1、语义化标签(hrader、footer等),使得页面的内容结构化,见名知义;2、增强型表单,拥有多个新的表单Input输入类型,可提供更好的输入控制和验证;3、video和audio元素,提供了播放视频和音频文件的标准方法;4、Canvas绘图;5、SVG绘图;6、地理定位;7、拖放API;8、Web Worker;9、Web Storage等等。
2023-05-14

HTML5新特性有哪些

本篇内容主要讲解“HTML5新特性有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“HTML5新特性有哪些”吧!Web存储 它具有以下特征: 你可以通过属性和方法来使用 JavaScript
2023-06-04

JDK1.5有哪些新特性

本篇内容主要讲解“JDK1.5有哪些新特性”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JDK1.5有哪些新特性”吧!  1.泛型(Generic)  C++通过模板技术可以指定集合的元素类型,
2023-06-03

JMS新特性有哪些

这篇文章主要为大家展示了“JMS新特性有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JMS新特性有哪些”这篇文章吧。JMS(Java Message Service,Java消息服务)是J
2023-06-17

FlexBuilder4新特性有哪些

这篇文章给大家分享的是有关FlexBuilder4新特性有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。FlexBuilder4新特性在上节中,我介绍了FlexBuilder4的10个新特性,同时我也强调了这
2023-06-17

Kubernetes1.5有哪些新特性

这篇“Kubernetes1.5有哪些新特性”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Kubernetes1.5有哪些新
2023-06-28

Android4.3新特性有哪些

Android 4.3(又名Jelly Bean)引入了以下一些新特性:1. 多用户支持:Android 4.3允许在同一设备上创建多个用户帐户,每个用户都有自己的个性化设置、应用和数据。2. 蓝牙低功耗(BLE):Android 4.3支
2023-09-25

FlexSDK4新特性有哪些

这篇文章主要介绍FlexSDK4新特性有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!FlexSDK4新特性FlexSDK4新特性一、主题在FlashBuilder4以前,Adobe默认的主题是Halo,而从Fl
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录