当前位置: 首页 > news >正文

FreeRTOS学习 --- 消息队列

队列简介

        队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递

         全局变量的弊端数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损

        使用队列的情况如下:

 

        读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用!

        FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、

二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。

        在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。

        在创建队列时,就要指定队列长度以及队列项目的大小!

FreeRTOS队列特点:

1、数据入队出队方式

        队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;

2、数据传递方式

        FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递

3、多任务访问

        队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息

4、出队、入队阻塞

        当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队

若阻塞时间为0  :直接返回不会等待;
若阻塞时间为0~port_MAX_DELAY  :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
若阻塞时间为port_MAX_DELAY  :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

入队阻塞:

        

队列满了,此时写不进去数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToSend

出队阻塞:

队列为空,此时读取不了数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToReceive

问题:当多个任务写入消息给一个满队列时,这些任务都会进入阻塞状态,也就是说有多个任务    在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?

答:

          1、优先级最高的任务

          2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态

队列结构体介绍

typedef struct QueueDefinition 
{int8_t * pcHead					/* 存储区域的起始地址 */int8_t * pcWriteTo;        				/* 下一个写入的位置 */union{QueuePointers_t     xQueue; SemaphoreData_t  xSemaphore; } u ;List_t xTasksWaitingToSend; 			/* 等待发送列表 */List_t xTasksWaitingToReceive;			/* 等待接收列表 */volatile UBaseType_t uxMessagesWaiting; 	/* 非空闲队列项目的数量 */UBaseType_t uxLength;			/* 队列长度 */UBaseType_t uxItemSize;                 		/* 队列项目的大小 */volatile int8_t cRxLock; 				/* 读取上锁计数器 */volatile int8_t cTxLock;			/* 写入上锁计数器 *//* 其他的一些条件编译 */
} xQUEUE;

        当我们锁住队列的时候,你是可以正常读写队列的,只不过操作不了等待发送\接收列表。

当用于队列使用时:

typedef struct QueuePointers
{int8_t * pcTail; 				/* 存储区的结束地址 */int8_t * pcReadFrom;			/* 最后一个读取队列的地址 */
} QueuePointers_t;

当用于互斥信号量和递归互斥信号量时 :

typedef struct SemaphoreData
{TaskHandle_t xMutexHolder;		/* 互斥信号量持有者 */UBaseType_t uxRecursiveCallCount;	/* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;

队列结构体整体示意图:

队列相关API函数介绍

        使用队列的主要流程:创建队列 ---> 写队列 ---> 读队列。

创建队列相关API函数介绍:

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

         动态和静态创建队列之间的区别:队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。

创建队列函数入口参数解析:

#define xQueueCreate (  uxQueueLength,   uxItemSize  )   			 \					xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) 

        此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配

        前面说 FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:

#define queueQUEUE_TYPE_BASE                  	( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  	( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 	( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       	( ( uint8_t ) 4U )	/* 递归互斥信号量 */

往队列写入消息API函数:

函数

描述

xQueueSend()

往队列的尾部写入消息

xQueueSendToBack()

xQueueSend()

xQueueSendToFront()

往队列的头部写入消息

xQueueOverwrite()

覆写队列消息(只用于队列长度为 1 的情况)

xQueueSendFromISR()

在中断中往队列的尾部写入消息

xQueueSendToBackFromISR()

xQueueSendFromISR()

xQueueSendToFrontFromISR()

在中断中往队列的头部写入消息

xQueueOverwriteFromISR()

在中断中覆写队列消息(只用于队列长度为 1 的情况)

 队列写入消息:

#define  xQueueSend(  xQueue,   pvItemToQueue,   xTicksToWait  )	 					\    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define  xQueueSendToBack(  xQueue,   pvItemToQueue,   xTicksToWait  )					 \    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define  xQueueSendToFront(  xQueue,   pvItemToQueue,   xTicksToWait  ) 					\   xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define  xQueueOverwrite(  xQueue,   pvItemToQueue  ) 								\    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

        可以看到这几个写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置!

        队列一共有 3 种写入位置 :

#define queueSEND_TO_BACK         ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT        ( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE            ( ( BaseType_t ) 2 )		/* 覆写队列*/

        注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用

往队列写入消息函数入口参数解析:

BaseType_t  xQueueGenericSend(  QueueHandle_t 	    xQueue,const void * const 	pvItemToQueue,TickType_t 		    xTicksToWait,const BaseType_t 	xCopyPosition   );

从队列读取消息API函数:

函数

描述

xQueueReceive()

从队列头部读取消息,并删除消息

xQueuePeek()

从队列头部读取消息

xQueueReceiveFromISR()

在中断中从队列头部读取消息,并删除消息

xQueuePeekFromISR()

在中断中从队列头部读取消息

队列读取消息函数入口参数解析:

BaseType_t    xQueueReceive( QueueHandle_t   xQueue,  void *          const pvBuffer,  TickType_t      xTicksToWait )

        此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。

BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const    pvBuffer,   TickType_t      xTicksToWait )

        此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息

队列相关API函数工作流程具体解析:

创建队列:xQueueCreate( )

        实际执行的是xQueueGenericCreate( )

xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

        1、计算队列需要多大内存 xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize )

        2、为队列申请内存,申请大小:sizeof( Queue_t ) + xQueueSizeInBytes ,前面部分存放结构体成员,后面就是队列项大小

        3、判断内存是否申请成功,成功即计算出队列项存储区的首地址

        4、调用prvInitialiseNewQueue()初始化新队列pxNewQueue

                (1)、初始化队列结构体成员变量

                (2)、调用xQueueGenericReset()复位队列

                        1)、初始化其他队列结构体成员变量

                        2)、判断要复位的队列是否为新创建的队列

                                不是新创建队列,那就复位它,将列表xTasksWaitingToSend移除,是新创建的队列,那就初始化这两个列表xTasksWaitingToSend和xTasksWaitingToReceive

往队列写入数据(入队):xQueueSend( )

        实际执行的是:xQueueGenericSend( )

xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

        1、进入临界区(关中断)

        2、判断队列是否已满?

        3、队列有空闲位置
                (1)、只有在队列有空闲位置或为覆写的情况才能写入消息

                (2)、当有空闲位置或覆写时:将待写入消息按指定写入方式复制到队列中

                (3)、判断是否有因为读不到消息而阻塞的任务,有的话,将解除阻塞态,通过这个函数

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理
                (4)、退出临界区(开中断)

        3、队列已满

                (1)、此时不能写入消息,因此要将任务阻塞

                (2)、如果阻塞时间为0 ,代表不阻塞,直接返回队列满错误

                (3)、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿(补偿就是计算剩余的阻塞时间还有多久)

                (4)、判断阻塞时间补偿后,是否还需要阻塞

                        需要:将任务的事件列表项添加到等待发送列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器。不需要:队列解锁,恢复调度器,返回队列满错误。

                        不需要:队列解锁,恢复调度器,返回队列满错误
 

  从队列读取数据(出队):xQueueReceive( )

        1、进入临界区(关中断)

        2、判断队列是否为空

        3、有数据

                (1)、使用函数prvCopyDataFromQueue( )拷贝数据

                (2)、队列项目个数减一

                (3)、因为前面已经减了一个队列项,所以队列已经有空位了,如果xTasksWaitingToSend等待发送列表中,有任务,则解除阻塞态,通过这个函数xTaskRemoveFromEventList( )

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理

                (4)、退出临界区(开中断)

        4、为空

                (1)、此时读取不到消息,因此要将任务阻塞

                (2)、如果阻塞时间为0 ,代表不阻塞,直接返回队列空错误

                (3)、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿

                (4)、判断阻塞时间补偿后,是否还需要阻塞

                        需要:将任务的事件列表项添加到等待接收列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器。

                        不需要:队列解锁,恢复调度器,返回队列空错误

相关文章:

FreeRTOS学习 --- 消息队列

队列简介 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递) 全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损 使用队列的情况如下:…...

PHP If...Else 语句详解

PHP If...Else 语句详解 引言 在PHP编程中,if...else语句是流程控制的重要组成部分,它允许程序根据条件判断执行不同的代码块。本文将详细解析PHP中的if...else语句,包括其基本用法、高级技巧以及注意事项。 一、基本用法 if...else语句的…...

pytorch使用SVM实现文本分类

人工智能例子汇总:AI常见的算法和例子-CSDN博客 完整代码: import torch import torch.nn as nn import torch.optim as optim import jieba import numpy as np from sklearn.model_selection import train_test_split from sklearn.feature_extract…...

安卓(android)读取手机通讯录【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的(如果代码有错漏,可在代码地址查看) 1.熟悉内容提供者(Content Provider)的概念和作用。 2.掌握内容提供者的创建和使用方法。 4.掌握内容URI的结构和用途。 二、实验条件 1.熟悉内容提供者的工作原理。 2.掌握内容提供者访问其…...

【Qt】常用的容器

Qt提供了多个基于模板的容器类&#xff0c;这些容器类可用于存储指定类型的数据项。例如常用的字符串列表类 QStringList 可用来操作一个 QList<QString>列表。 Qt的容器类比标准模板库(standard template library&#xff0c;STL)中的容器类更轻巧、使用更安全且更易于使…...

基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 3.核心程序 .…...

分布式事务组件Seata简介与使用,搭配Nacos统一管理服务端和客户端配置

文章目录 一. Seata简介二. 官方文档三. Seata分布式事务代码实现0. 环境简介1. 添加undo_log表2. 添加依赖3. 添加配置4. 开启Seata事务管理5. 启动演示 四. Seata Server配置Nacos1. 修改配置类型2. 创建Nacos配置 五. Seata Client配置Nacos1. 增加Seata关联Nacos的配置2. 在…...

JavaScript常用的内置构造函数

JavaScript作为一种广泛应用的编程语言&#xff0c;提供了丰富的内置构造函数&#xff0c;帮助开发者处理不同类型的数据和操作。这些内置构造函数在创建和操作对象时非常有用。本文将详细介绍JavaScript中常用的内置构造函数及其用途。 常用内置构造函数概述 1. Object Obj…...

25寒假算法刷题 | Day1 | LeetCode 240. 搜索二维矩阵 II,148. 排序链表

目录 240. 搜索二维矩阵 II题目描述题解 148. 排序链表题目描述题解 240. 搜索二维矩阵 II 点此跳转题目链接 题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到…...

MQTT知识

MQTT协议 MQTT 是一种基于发布/订阅模式的轻量级消息传输协议&#xff0c;专门针对低带宽和不稳定网络环境的物联网应用而设计&#xff0c;可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、智慧城市、远程医疗、…...

【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...

新一代搜索引擎,是 ES 的15倍?

Manticore Search介绍 Manticore Search 是一个使用 C 开发的高性能搜索引擎&#xff0c;创建于 2017 年&#xff0c;其前身是 Sphinx Search 。Manticore Search 充分利用了 Sphinx&#xff0c;显着改进了它的功能&#xff0c;修复了数百个错误&#xff0c;几乎完全重写了代码…...

使用 Context API 管理临时状态,避免 Redux/Zustand 的持久化陷阱

在开发 React Native 应用时&#xff0c;我们经常需要管理全局状态&#xff0c;比如用户信息、主题设置、网络状态等。而对于某些临时状态&#xff0c;例如 数据同步进行中的状态 (isSyncing)&#xff0c;我们应该选择什么方式来管理它&#xff1f; 在项目开发过程中&#xff…...

PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统

基于YOLOv8深度学习的学生课堂行为检测识别系统&#xff0c;其能识别三种学生课堂行为&#xff1a;names: [举手, 读书, 写字] 具体图片见如下&#xff1a; 第一步&#xff1a;YOLOv8介绍 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本…...

word2vec 实战应用介绍

Word2Vec 是一种由 Google 在 2013 年推出的重要词嵌入模型,通过将单词映射为低维向量,实现了对自然语言处理任务的高效支持。其核心思想是利用深度学习技术,通过训练大量文本数据,将单词表示为稠密的向量形式,从而捕捉单词之间的语义和语法关系。以下是关于 Word2Vec 实战…...

C# 操作符重载对象详解

.NET学习资料 .NET学习资料 .NET学习资料 一、操作符重载的概念 在 C# 中&#xff0c;操作符重载允许我们为自定义的类或结构体定义操作符的行为。通常&#xff0c;我们熟悉的操作符&#xff0c;如加法&#xff08;&#xff09;、减法&#xff08;-&#xff09;、乘法&#…...

python学opencv|读取图像(五十四)使用cv2.blur()函数实现图像像素均值处理

【1】引言 前序学习进程中&#xff0c;对图像的操作均基于各个像素点上的BGR值不同而展开。 对于彩色图像&#xff0c;每个像素点上的BGR值为三个整数&#xff0c;因为是三通道图像&#xff1b;对于灰度图像&#xff0c;各个像素上的BGR值是一个整数&#xff0c;因为这是单通…...

CNN的各种知识点(四): 非极大值抑制(Non-Maximum Suppression, NMS)

非极大值抑制&#xff08;Non-Maximum Suppression, NMS&#xff09; 1. 非极大值抑制&#xff08;Non-Maximum Suppression, NMS&#xff09;概念&#xff1a;算法步骤&#xff1a;具体例子&#xff1a;PyTorch实现&#xff1a; 总结&#xff1a; 1. 非极大值抑制&#xff08;…...

虚幻基础16:locomotion direction

locomotion locomotion&#xff1a;角色运动系统的总称&#xff1a;包含移动、奔跑、跳跃、转向等。 locomotion direction 玩家输入 玩家输入&#xff1a;通常代表玩家想要的移动方向。 direction 可以计算当前朝向与移动方向的Δ。从而实现朝向与移动(玩家输入)方向的分…...

C++游戏开发实战:从引擎架构到物理碰撞

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 C 是游戏开发中最受欢迎的编程语言之一&#xff0c;因其高性能、低延迟和强大的底层控制能力&#xff0c;被广泛用于游戏…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...