Swift 异步序列 AsyncStream 新“玩法”以及内存泄漏、死循环那些事儿(上)

在这里插入图片描述

概览

异步序列(Async Sequence)是 Swift 5.5 新并发模型中的一员“悍将”,系统标准库中很多类都做了重构以支持异步序列。我们还可以用 AsyncStream 辅助结构非常方便的创建自己的异步序列。

在这里插入图片描述

这里我们就来一起聊聊 AsyncStream 结构,以及它新增的 makeStream 构建器方法。

在本篇博文中,您将学到如下内容

  • 概览
  • 1. AsyncStream 旧构造器的弊端
  • 2. 拯救者:新方法 makeStream!
  • 总结

而在下篇中,我们将再接再厉继续讨论异步序列在使用时可能产生的内存泄漏、无限循环等等那些的潜伏陷阱。

相信学完本系列课程后,大家会对 Swift 新异步并发模型中异步序列的正确使用有更为深刻的领悟。

那还等什么呢?Let‘s find out!!!😉


1. AsyncStream 旧构造器的弊端

在 Swift 中创建自定义异步序列有很多种“姿势”,其中一个常见的方法是使用 AsyncStream 结构,可以认为它是一个异步序列的辅助构造器:

在这里插入图片描述

我们知道异步序列中的核心和精髓就是它的 Continuation 对象,做一个“二次元卡哇伊”的比喻:如果异步序列是一只大螃蟹,则 Continuation 就是它肥得流油的“蟹黄”:

在这里插入图片描述

值得注意的是,不像 Swift 中其它连续体(Continuation)对象,AsyncStream.Continuation 支持可逃逸(escaping)特性。这就让它的使用灵活性更上了一个层次。

我们使用 AsyncStream 创建异步序列主要有两种场景,一种是直接在其创建时就“包办”固定好所有元素的产出,但这样做缺乏变数、比较“死板”:

let stream = AsyncStream(unfolding: {
    return Int.random(in: 0..<Int.max)
})

另一种场景多半被用在 Apple 开发中的代理(Delegate)模式中,这种方式更加灵动自如:

protocol NumberSpawnerDelegate {
    func spawn(_ numbers: [Int])
}

struct Spawner {
    let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
    var delegator: NumberSpawnerDelegate?
    var cancel: Cancellable?
    
    mutating func setup() {
        cancel = timer.sink { [self] _ in
            var numbers = [Int]()
            for _ in 0..<Int.random(in: 1...3) {
                numbers.append(Int.random(in: 0...10000))
            }
            self.delegator?.spawn(numbers)
        }
    }
}

class AsyncNumberStream: NumberSpawnerDelegate {
    var continuation: AsyncStream<Int>.Continuation?
    
    lazy var stream: AsyncStream<Int> = {
        AsyncStream { continuation in
            self.continuation = continuation
        }
    }()
    
    func spawn(_ numbers: [Int]) {
        for i in numbers {
            continuation?.yield(i)
        }
    }
}

如上代码所示,我们的 AsyncNumberStream 异步序列遵从于 NumberSpawnerDelegate 协议,而 Spawner 作为驱动者自然就成为了 AsyncNumberStream 的事件源,它通过调用协议中的 spawn(😃 方法连接了发布者和接受者,使得天堑变通途。

我们可以这样使用 AsyncNumberStream 异步序列:

Task {
    let stream = AsyncNumberStream()
    var spawner = Spawner()
    spawner.delegator = stream
    spawner.setup()
    
    for await i in stream.stream {
        print("\(i)")
    }
}

运行结果如下所示:

在这里插入图片描述

不过这种以 AsyncStream 构造器“抓取”其 Continuation 对象的方式略显别扭(合肥话叫“肘手”)。而且 continuation 属性类型需要设置为可选值(AsyncStream<Int>.Continuation?),这多少让人觉得有些“不畅快”。

2. 拯救者:新方法 makeStream!

从 iOS 17.0 开始 Apple 为 AsyncStream 添加了一个新的 makeStream 方法专门用来解决上述窘境:

在这里插入图片描述

值得注意的是,虽然 makeStream 在 iOS 17 才被加入,但它向后兼容旧的系统(iOS 13 - iOS 17),所以在之前的 iOS 中也可以任性的使用它。

该方法返回一个由异步序列和其对应连续体组成的元组:

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    @_backDeploy(before: macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0)
    public static func makeStream(of elementType: Element.Type = Element.self, bufferingPolicy limit: AsyncStream<Element>.Continuation.BufferingPolicy = .unbounded) -> (stream: AsyncStream<Element>, continuation: AsyncStream<Element>.Continuation)

这意味着之前“肘手”的调用可以改成这样:

class AsyncNumberStream: NumberSpawnerDelegate {
    let stream: AsyncStream<Int>
    private let continuation: AsyncStream<Int>.Continuation
    
    init() {
        let (stream, continuation) = AsyncStream.makeStream(of: Int.self)
        self.stream = stream
        self.continuation = continuation
    }
    
    func spawn(_ numbers: [Int]) {
        for i in numbers {
            continuation.yield(i)
        }
    }
}

从上面代码可以看到,AsyncStream.makeStream 方法带来了如下一些改变:

  • Continuation 不再“嵌入”在 AsyncStream 构造器的回调闭包之中,它们现在处在同一个层级;
  • continuation 属性不再要求是可选类型了;
  • 整体实现更加简单、一目了然;

现在,我们对 AsyncStream.Continuation 的获取不再聱牙诘屈,同时也完美的消除了 continuation 属性可选类型的限制,正谓是一举两得、一石二鸟也!

当然,可能有的小伙伴们觉得 AsyncStream.makeStream 方法如下形式的调用更加 nice 一些:

init() {
	let result = AsyncStream.makeStream(of: UUID.self)
	locations = result.stream
	continuation = result.continuation
}

值得一提的是,尽管我们将 AsyncNumberStream 内部的逻辑“粉饰一新”,但外部接口并没有丝毫改变。所以,之前的调用无需做任何修改。

编译运行代码可以发现,一切都未曾改变,正所谓平平淡淡才是真!棒棒哒!

虽然新的 makeStream 方法让我们原有的实现“清风徐来,水波不兴”,但异步序列本身的使用仍然暗影重重、波诡云谲。康庄大道上还有很多陷阱等着算计我们,我们将在下篇博文中将它们一网打尽!

总结

在本篇博文中,我们讨论了 Swift 5.5 新并发模型中用 AsyncStream 结构创建异步序列的新方法,并比较了它和之前旧的实现有哪些进步。

在下篇博文中,我们将继续异步序列的填坑之旅,期待吧!

感谢观赏,再会!😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/528591.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

领鸡蛋游戏养鸡游戏淘宝客源码广告联盟

用户中心 用户信息&#xff1a;显示用户名、头像、鸡蛋数量、足迹等基本信息。我的足迹&#xff1a;展示用户的饲料获取记录明细&#xff0c;包括来源、数量和时间。我的好友&#xff1a;展示邀请的好友列表&#xff0c;支持好友间互动&#xff0c;如串门、偷取/赠送饲料&#…

贪心算法|135.分发糖果

力扣题目链接 class Solution { public:int candy(vector<int>& ratings) {vector<int> candyVec(ratings.size(), 1);// 从前向后for (int i 1; i < ratings.size(); i) {if (ratings[i] > ratings[i - 1]) candyVec[i] candyVec[i - 1] 1;}// 从后…

【科东软件】鸿道Intewell-Lin_V2.2.0 软件版本发布

鸿道操作系统 Intewell-Lin_V2.2.0 软件版本发布 Intewell-Lin_V2.2.0 版本号&#xff1a;V2.2.0 版本或修改说明 增加功能&#xff1a; 1、增加T3板级支持 支持硬件列表

AI绘画工具的兴起与应用:热门AI绘画生成器推荐及使用指南

文章目录 一、AI绘画工具概述二、热门AI绘画生成器推荐2.1、DALL-E22.2、DeepDreamGenerator2.3、Artbreeder2.4、BigSleep2.5、NightCafe2.6、DeepAI2.7、触站AI2.8、美术加AI2.9、文心一格 三、如何使用AI绘画生成器3.1、选择AI绘画生成器3.2、描述画面内容3.3、选择绘画风格…

【Spring Cloud】服务容错中间件Sentinel入门

文章目录 什么是 SentinelSentinel 具有以下特征&#xff1a;Sentinel分为两个部分: 安装 Sentinel 控制台下载jar包&#xff0c;解压到文件夹启动控制台访问了解控制台的使用原理 微服务集成 Sentinel添加依赖增加配置测试用例编写启动程序 实现接口限流总结 欢迎来到阿Q社区 …

为什么 C/C++ 的库很喜欢缩写?

一、正如很多回答已经提到的&#xff0c;早期的有效标识符长度有限制&#xff0c;所以缩写用得比较多。也主要是在 C 里&#xff08;Unix 的传统&#xff09;。C 里的标识符用缩写的不多。如 C98&#xff08;毕竟比 C89 晚了 9 年么&#xff09;里我们就已经有了很多挺长的名字…

Java面试必问题29:MySQL篇(重点必问)

数据库的ACID特性 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务中的操作要么全部成功&#xff0c;要么全部失败。事务是一个不可分割的单元&#xff0c;要么全部执行&#xff0c;要么全部回滚。如果事务中的任何操作失败&#xff0c;所有操作都将被回滚到事务开始…

​5种常用于LLM的令牌遮蔽技术介绍以及Pytorch的实现

本文将介绍大语言模型中使用的不同令牌遮蔽技术&#xff0c;并比较它们的优点&#xff0c;以及使用Pytorch实现以了解它们的底层工作原理。 令牌掩码Token Masking是一种广泛应用于语言模型分类变体和生成模型训练的策略。BERT语言模型首先使用&#xff0c;并被用于许多变体(R…

python|enumerate

enumerate可以用来列举可遍历的对象 # 假设我们有一个列表 fruits [apple, banana, cherry] # 使用enumerate()函数遍历列表 for index, fruit in enumerate(fruits): print(f"Index: {index}, Fruit: {fruit}")# 假设我们想要从1开始计数 for index, fru…

使用ADO.NET访问数据库

目录 访问数据库的步骤 &#xff11;、建立数据库 &#xff12;、设置链接参数 &#xff08;1&#xff09;web网页和数据库连接的方法一 &#xff08;2&#xff09;web网页和数据库连接的方法二 &#xff13;、建立链接对象 &#xff14;、显示数据库 &#xff15;、数…

第十五讲:C语言内存函数

目录 1、C语言内存函数 1.1、memcpy函数的使用和模拟 1.2、memmove函数的使用和模拟 1.3、memset函数的使用 1.4、memcmp函数的使用 1、C语言内存函数 注意&#xff1a;下面这些函数的使用要包含头文件&#xff1a;string.h 1.1、memcpy函数的使用和模拟 函数声明为&am…

echarts 条形图(柱状图)多个图例按钮默认高亮一个,且只能高亮一个

核心&#xff1a;给图例按钮添加点击事件 myChart.on("legendselectchanged", function (params) {let selected {功率柜: true,母线柜: false,充电桩终端: false,网络柜: false,};for (let key in selected) {if (key ! params.name) {myChart.setOption({legend:…

vue3使用jsQR解析二维码

1.了解jsQR jsQR是一个纯javascript脚本实现的二维码识别库,不仅可以在浏览器端使用,而且支持后端node.js环境。jsQR使用较为简单,有着不错的识别率。 2.效果图 3.二维码 4.下载jsqr包 npm i -d jsqr5.代码 <script setup> import {ref } from vue import jsQR fr…

视频图像的两种表示方式YUV与RGB(1)

了解过计算机图形图像学的该知道&#xff0c;可用RGB和YUV两种方式表示图像像素&#xff0c;视频由一帧一帧的图像组成&#xff0c;每一张图片是一个一个的像素点组成&#xff0c;既然有两种表示像素的方法&#xff0c;那就一起解一下两种表示方式的异同及优缺点。 RGB像素 这…

【PPT技巧】如何取消PPT的密码保护?

PPT文件有两种密码&#xff0c;一种是打开密码、一种是修改权限。今天分享这两种密码如何取消。 首先需要告知大家的是&#xff0c;密码的取消需要输入正确的密码。 打开密码的取消&#xff0c;我们需要先输入密码&#xff0c;打开文件&#xff0c;然后点击文件 – 信息 – 保…

os.listdir()bug总结

今天测试出一个神奇的bug&#xff0c;算是教训吧&#xff0c;找了两天不知道问题在哪&#xff0c;最后才发现问题出现在这 原始文件夹显示 os.listdir()结果乱序 import os base_path "./file/"files os.listdir(base_path)print(files)问题原因 解决办法(排序) …

YOLOv8全网独家改进: 小目标 |新颖的多尺度前馈网络(MSFN) | 2024年4月最新成果

💡💡💡本文独家改进:多尺度前馈网络(MSFN),通过提取不同尺度的特征来增强特征提取能力,2024年最新的改进思路 💡💡💡创新点:多尺度前馈网络创新十足,抢先使用 💡💡💡如何跟YOLOv8结合:1)放在backbone后增强对全局和局部特征的提取能力;2)放在detect…

解密项目管理专业术语:十大名词背后的实战技巧

项目管理是一门综合学科&#xff0c;涵盖了一系列方法、技能和工具。今天为大家带来项目管理的十大专业术语&#xff0c;它们分别是项目范围、利益相关者管理、工作分解结构&#xff08;WBS&#xff09;、里程碑、风险管理、资源分配、关键路径法&#xff08;CPM&#xff09;、…

漏洞挖掘 | 从信息收集到登录后台

通过信息收集进网站后台 记录一次通过简单的信息收集获取账号信息后进入后台的经过。打开思路&#xff0c;利用我们所有能够捕获的信息并串联起来。 0X0 开始&#xff1a; 打开网站发现是一个登录网站 随便输入账号密码&#xff0c;发现有用户名枚举&#xff1a; 0X1 获取ic…

SuperMap GIS基础产品FAQ集锦(202403)

一、SuperMap GIS基础产品桌面GIS-FAQ集锦 问题1&#xff1a;【iDesktop】安装了idesktop 11i&#xff0c;现想进行插件开发&#xff0c;根据安装指南安装SuperMap.Tools.RegisterTemplate.exe&#xff0c;运行多次均失败 【问题原因】该脚本是之前老版本针对VS2010写的&…