[iOS开发]UITableView的性能优化
一些基础的优化
(一)CPU
1. 用轻量级对象
比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
CALayer * imageLayer = [CALayer layer];
imageLayer.bounds = CGRectMake(0,0,200,100);
imageLayer.position = CGPointMake(200,200);
imageLayer.contents = (id)[UIImage imageNamed:@"xx.jpg"].CGImage;
imageLayer.contentsGravity = kCAGravityResizeAspect;
[tableCell.contentView.layer addSublayer:imageLayer];
2. 不要频繁地调用UIView的相关属性
比如 frame
、bounds
、transform
等属性,尽量减少不必要的修改
不要给UITableViewCell
动态添加subView
,可以在初始化UITableViewCell
的时候就将所有需要展示的添加完毕,然后根据需要来设置hidden
属性显示和隐藏
3. 提前计算好布局
在滑动时,会不断调用heightForRowAtIndexPath:
,当Cell高度需要自适应时,每次回调都要计算高度,会导致UI卡顿。为了避免重复无意义的计算,需要缓存高度。
UITableViewCell
高度计算主要有两种,一种固定高度,另外一种动态高度。
固定高度:
rowHeight
高度默认44
对于固定高度直接采用self.tableView.rowHeight = 77
比tableView:heightForRowAtIndexPath:
更高效
动态高度:
采用tableView:heightForRowAtIndexPath:
这种代理方式,设置这种代理之后rowHeight
则无效,需要满足以下三个条件
- 使用Autolayout进行UI布局约束(要求
cell.contentView
的四条边都与内部元素有约束关系) - 指定TableView的
estimatedRowHeight
属性的默认值 - 指定TableView的
rowHeight
属性为UITableViewAutomaticDimension
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44;
除了提高cell
高度的计算效率之外,对于已经计算出的高度,我们需要进行缓存
4. 直接设置frame
Autolayout 会比直接设置 frame
消耗更多的 CPU 资源
5. 图片尺寸合适
图片的 size
最好刚好跟 UIImageView
的 size
保持一致
图片通过contentMode
处理显示,对tableview
滚动速度同样会造成影响
从网络下载图片后先根据需要显示的图片大小切/压缩成合适大小的图,每次只显示处理过大小的图片,当查看大图时在显示大图。
服务器直接返回预处理好的小图和大图以及对应的尺寸最好
/// 根据特定的区域对图片进行裁剪
+ (UIImage*)kj_cutImageWithImage:(UIImage*)image Frame:(CGRect)cropRect{return ({CGImageRef tmp = CGImageCreateWithImageInRect([image CGImage], cropRect);UIImage *newImage = [UIImage imageWithCGImage:tmp scale:image.scale orientation:image.imageOrientation];CGImageRelease(tmp);newImage;});
}
6. 控制最大并发数量
控制一下线程的最大并发数量,当下载线程数超过2时,会显著影响主线程的性能。可以用一个NSOperationQueue
来维护下载请求,并设置其最大线程数maxConcurrentOperationCount
。
当然在不需要响应用户请求时,也可以增加下载线程数来加快下载速度:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{if (!decelerate) self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{self.queue.maxConcurrentOperationCount = 2;
}
7. 子线程处理
尽量把耗时的操作放到子线程
- 文本处理(尺寸计算、绘制)
- 图片处理(解码、绘制)
8. 异步绘制
异步绘制,就是异步在画布上绘制内容,将复杂的绘制过程放到后台线程中执行,然后在主线程显示。
// 异步绘制,切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{UIGraphicsBeginImageContextWithOptions(size, NO, scale);CGContextRef context = UIGraphicsGetCurrentContext();// TODO:draw in context...CGImageRef imgRef = CGBitmapContextCreateImage(context);UIGraphicsEndImageContext();dispatch_async(dispatch_get_main_queue(), ^{self.layer.contents = imgRef;});
});
(二)GPU
1. 避免短时间内大量显示图片
尽可能将多张图片合成一张进行显示。
2. 控制尺寸
GPU能处理的最大纹理尺寸是4096x4096,超过这个尺寸就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸。
3. 减少图层混合操作
当多个视图叠加,放在上面的视图是半透明的,那么这个时候GPU就要进行混合,把透明的颜色加上放在下面的视图的颜色混合之后得出一个颜色再显示在屏幕上,这一步是消耗GPU资源
UIView
的backgroundColor
不要设置为clearColor
,最好设置和superView
的backgroundColor
颜色一样- 图片避免使用带
alpha
通道的图片
4. 透明处理
减少透明的视图,不透明的就设置opaque = YES
5. 避免离屏渲染
离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作。离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕切换到离屏;等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕。
(1)下面的情况或操作会引发离屏渲染
- 光栅化,
layer.shouldRasterize = YES
- 遮罩,
layer.mask
- 圆角,同时设置
layer.masksToBounds = YES
和layer.cornerRadius > 0
- 阴影,
layer.shadow
layer.allowsGroupOpacity = YES
和layer.opacity != 1
- 重写
drawRect
方法
(2)圆角优化
这里主要其实就是解决同时设置layer.masksToBounds = YES
和 layer.cornerRadius > 0
就会产生的离屏渲染。其实我们在使用常规视图切圆角时,可以只使用view.layer.cornerRadius = 3.0
,这时是不会产生离屏渲染。但是UIImageView
有点特殊,切圆角时必须上面2句同时设置,则会产生离屏渲染,所以我们可以考虑通过 CoreGraphics
绘制裁剪圆角,或者叫美工提供圆角图片。
- (UIImage *)billy_ellipseImage {UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);CGContextRef ctx = UIGraphicsGetCurrentContext();CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);CGContextAddEllipseInRect(ctx, rect);CGContextClip(ctx);[self drawInRect:rect];UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return image;
}
此外,还可以通过贝塞尔曲线画圆角:
- (void)clipCornerWithImageView:(UIImageView *)originViewandTopLeft:(BOOL)topLeftandTopRight:(BOOL)topRightandBottomLeft:(BOOL)bottomLeftandBottomRight:(BOOL)bottomRightcornerRadius:(CGFloat)radius
{CGRect rect = originView.bounds;UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];// 创建遮罩层CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];maskLayer.frame = rect;maskLayer.path = maskPath.CGPath; // 轨迹originView.layer.mask = maskLayer;
}- (void)clipCornerWithImageView:(UIImageView *)originViewcornerRadius:(CGFloat)radius {CGRect rect = originView.bounds;UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];// 创建遮罩层CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];maskLayer.frame = rect;maskLayer.path = maskPath.CGPath; // 轨迹originView.layer.mask = maskLayer;
}
这样还可以控制特定角是否设置圆角。这种情况有个弊端,就是切割角度有限,所以实现大角度圆角只能采取自己画线的方式来操作。
(3)阴影优化
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
(4)强制开启光栅化
当图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十分消耗性能,这时就可以选择强制开启光栅化layer.shouldRasterize = YES
。
当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存,但是如果图层发生改变的时候就会重新产生位图缓存。
所以这个功能一般不能用于UITableViewCell中,复用反而降低了性能。最好用于图层较多的静态内容的图形。
(5)优化建议
- 使用中间透明图片蒙上去达到圆角效果
- 使用ShadowPath指定layer阴影效果路径
- 使用异步进行layer渲染
- 将UITableViewCell及其子视图的opaque属性设为YES,减少复杂图层合成
- 尽量使用不包含透明alpha通道的图片资源
- 尽量设置layer的大小值为整形值
- 背景色的alpha值应该为1,例如不要使用clearColor
- 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案
- 很多情况下用户上传图片进行显示,可以让服务端处理圆角
加载图片的特殊需求
对于没有大型项目经验的我,很难触碰到设备的性能瓶颈,可是未来接触的项目里需要处理的数据会有很多,可能会有各种特殊的需求,比如要求实现: 1. 要求 `tableView` 滚动的时候,滚动到哪行,哪行的图片才加载并显示,滚动过程中图片不加载显示; 2. 页面跳转的时候,取消当前页面的图片加载请求;
先来看看一般的加载逻辑,放一段我之前写过的项目的代码:
如上设置,如果我们有20行cell
,页面启动的时候,直接滑动到最底部,20个cell
都进入过了界面,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
这个方法就会被调用20次,不符合需求1。
为此,我学习了一下,学习到了两个解决方案,并自己动手实践了一下:
Runloop的小技巧
runloop
- 两种常用模式介绍: trackingMode
&& defaultRunLoopMode
- 默认情况 - defaultRunLoopMode
- 滚动时候 - trackingMode
滚动的时候,进入trackingMode
,这会导致defaultMode
下的任务会被暂停,停止滚动的时候再次进入defaultMode
并继续执行defaultMode
下的任务。
代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];if (!cell) {cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];}DemoModel *model = self.datas[indexPath.row];cell.textLabel.text = model.text;if (model.iconImage) {cell.imageView.image = model.iconImage;} else {cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];[self performSelector:@selector(billy_loadImgeWithIndexPath:)withObject:indexPathafterDelay:0.0inModes:@[NSDefaultRunLoopMode]];}return cell;
}//下载图片,并渲染到cell上显示
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {DemoModel *model = self.datas[indexPath.row];UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];[ImageDownLoadManager runloop_loadImageWithModel:model success:^{//主线程刷新UIdispatch_async(dispatch_get_main_queue(), ^{cell.imageView.image = model.iconImage;//[cell layoutSubviews];});}];
}
其他办法
我们可以手动判断UITableView
的状态,保存下载任务,然后决定执行哪些下载任务或者在适当的时机取消这些任务。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];if (!cell) {cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];}DemoModel *model = self.datas[indexPath.row];cell.textLabel.text = model.text;if (model.iconImage) {cell.imageView.image = model.iconImage;} else {cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];// [self performSelector:@selector(billy_loadImgeWithIndexPath:)
// withObject:indexPath afterDelay:0.0
// inModes:@[NSDefaultRunLoopMode]];//拖动的时候不显示if (!tableView.dragging && !tableView.decelerating) {//下载图片数据[self billy_loadImgeWithIndexPath:indexPath];}}return cell;
}
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {DemoModel *model = self.datas[indexPath.row];//保存当前正在下载的操作ImageDownLoadManager *manager = self.imageLoadDic[indexPath];if (!manager) {manager = [[ImageDownLoadManager alloc] init];//开始加载-保存到当前下载操作字典中[self.imageLoadDic setObject:manager forKey:indexPath];}[manager loadImageWithModel:model success:^{//主线程刷新UIdispatch_async(dispatch_get_main_queue(), ^{UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];cell.imageView.image = model.iconImage;[cell layoutSubviews];});//加载成功-从保存的当前下载操作字典中移除[self.imageLoadDic removeObjectForKey:indexPath];}];
}
- (void)billy_loadImage {//拿到界面内-所有的cell的indexpathNSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;for (NSIndexPath *indexPath in visableCellIndexPaths) {DemoModel *model = self.datas[indexPath.row];if (model.iconImage) {continue;}[self billy_loadImgeWithIndexPath:indexPath];}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {if (!decelerate) {//直接停止-无动画[self billy_loadImage];}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{[self billy_loadImage];
}
界面消失:
- (void)viewWillDisappear:(BOOL)animated{[super viewWillDisappear:animated];NSArray *loadImageManagers = [self.imageLoadDic allValues];//当前图片下载操作全部取消[loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];
}
下面附上我demo的地址:https://github.com/BillyMiracle/TableViewImgLoadOptimization。欢迎大家下载一起学习。
先写这么多吧,学习的道路还长着呢。。。
相关文章:

[iOS开发]UITableView的性能优化
一些基础的优化 (一)CPU 1. 用轻量级对象 比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView CALayer * imageLayer [CALayer layer]; imageLayer.bounds CGRectMake(0,0,200,100); imageLayer.position CGPointMake(200,200…...

使用opencv实现图像滤波
1 图像滤波介绍 滤波是信号和图像处理中的基本任务之一,其旨在有选择地提取图像的某些特征,可以用于在给定应用程序的上下文中传达重要信息,例如,去除图像中的噪声、提取所需的视觉特征、图像重采样等。 1.1 图像滤波理论 图像…...

Swagger在php和java项目中的应用
Swagger在php和java项目中的应用 Swagger简介Swagger在java项目中的应用步骤常用注解 Swagger在php项目中的应用 Swagger简介 Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 总体目标是使客户端和文件系统作为服务器以…...

java科学计数法表示数值
Background 大多数计算器及计算机程序用科学记数法显示非常大和非常小的结果;但很多时候,我们需要做一个统一,要么全部以科学计数法输出,要么就全部显示为普通计数。注意:这里对大于等于1的数据做了特殊处理࿰…...

基于C#实现树状数组
有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的。 一、概序 假如我现在有个需求,就是要频繁的求数组的前 n 项和&#x…...

Ubuntu Server 20.04.6下Anaconda3安装Pytorch
环境 Ubuntu 20.04.6 LTS Anaconda3-2023.09-0-Linux-x86_64.sh conda 23.7.4 Pytorch 1.11.0 安装 先创建一个工作环境,环境名叫lia: conda create -n lia python3.8环境的使用方法如下: conda activate lia # 激活环境 conda deactiv…...

C#-关于日志的功能扩展
目录 一、日志Sink(接收器) 二、Trace追踪实现日志 三、日志滚动 一、日志Sink(接收器) 安装NuGet包:Serilog Sink有很多种,这里介绍两种: Console接收器(安装Serilog.Sinks.Console); File接收器(安装…...

小程序禁止二次转发分享私密消息动态消息
第一种用法:私密消息 私密消息:运营人员分享小程序到个人或群之后,该消息只能在被分享者或被分享群内打开,不可以二次转发。 用途:主要用于不希望目标客群外的人员看到的分享信息,比如带有较高金额活动的…...

普乐蛙绵阳科博会一场VR科普航天科学盛宴科普知识
普乐蛙绵阳科普展:一场科学盛宴,点燃孩子探索欲望的火花! 普乐蛙绵阳科普展正在如火如荼地进行中,吸引了无数孩子和家长的热情参与。这场科普盛宴以独特的内外视角,让人们感受到科学的魅力,激发了孩子们对知识的渴望和…...

FFNPEG编译脚本
下面是一个ffmpeg编译脚本: #!/bin/bash set -eu -o pipefail set eu o pipefailFFMPEG_TAGn4.5-dev build_path$1 git_repo"https://github.com/FFmpeg/FFmpeg.git" cache_tool"" sysroot"" c_compiler"gcc" cxx_compile…...

Python期末复习题库(下)——“Python”
小雅兰期末加油冲冲冲!!! 1. (单选题)下列关于文件打开模式的说法,错误的是( C )。 A. r代表以只读方式打开文件 B. w代表以只写方式打开文件 C. a代表以二进制形式打开文件 D. 模式中使用时,文件可读可写 2. (单选题)下列选项中,以追加…...

tauri中使用rust调用动态链接库例子(使用libloading库和libc库)
前言 当前采用桌面端框架位tauri,现在需要调用读卡器等硬件设备,硬件厂商提供了32位的动态链接库,现在记录例子,需要注意的点是使用libloading库和libc库, [package] name "yyt-device-rust" version &q…...

Leetcode—739.每日温度【中等】
2023每日刷题(四十二) Leetcode—739.每日温度 单调栈实现思想 从右到左实现代码 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {int n temperatures.size();stack<int> st;vector<i…...

毕业设计单片机可以用万能板吗?
毕业设计单片机可以用万能板吗? 可以是可以,就是焊接起来比较麻烦,特别是有好几个重复连线点的时候,检测起来就不那么容易了,而且布线看起来乱糟糟的,如果后期一不小心把线弄断了,查起来就更麻烦了&#x…...

spring boot整合Jasypt实现配置加密
文章目录 目录 文章目录 前言 一、Jasypt是什么? 二、使用步骤 1.引入 2.测试使用 3.结果 总结 前言 一、Jasypt是什么? Jasypt(Java Simplified Encryption)是一个Java库,提供了一种简单的加密解密方式,…...

java学校高校运动会报名信息管理系统springboot+jsp
课题研究方案: 结合用户的使用需求,本系统采用运用较为广泛的Java语言,springboot框架,HTML语言等关键技术,并在idea开发平台上设计与研发创业学院运动会管理系统。同时,使用MySQL数据库,设计实…...

Java(七)(Lambda表达式,正则表达式,集合(Collection,Collection的遍历方式))
目录 Lambda表达式 省略写法(要看懂) 正则表达式 语法 案例 正则表达式的搜索替换和分割内容 集合进阶 集合体系结构 Collection Collection的遍历方式 迭代器 增强for循环 Lambda表达式遍历Collection List集合 ArrayList LinkedList 哈希值 HashSet底层原理 …...

华为OD机试 - 二叉树计算(Java JS Python C)
目录 题目描述 输入描述 输出描述 用例 题目解析 JS算法源码 Java算法源码...

鸿蒙(HarmonyOS)应用开发——基础组件
组件 组件化是一种将复杂的前端应用程序分解成小的、独立的部分的方法。这些部分被称为组件,它们可以重复使用,可以与其他组件组合使用以创建更复杂的组件,并且它们有自己的生命周期和状态。 组件化的目的是提高开发效率和代码重用率&#…...

Vue3的项目创建到启动
Vue3的项目创建 检查node版本创建 npm init vuelatest 安装依赖 项目启动 启动成功...

开关电源基础而又硬核的知识
1.什么是Power Supply? Power Supply是一种提供电力能源的设备,它可以将一种电力能源形式转换成另外一种电力能源形式,并能对其进行控制和调节。 根据转换的形式分类:AC/DC、DC/DC、DC/AC、AC/AC 根据转换的方法分类:线性电源、…...

LightDB23.4 支持转换sql中中文空格和逗号为英文空格和逗号
功能介绍 在Lightdb数据库兼容Oracle的语法时,发现Oracle支持sql语句中使用中文空格和中文逗号,为了方便用户迁移到Lightdb,在Lightdb23.4版本中支持了转换中文空格和逗号的功能。该功能由GUC参数lightdb_convert_chinese_char来控制开关&am…...

EM@常见平面曲线的方程的不同表示方式
文章目录 abstract常见曲线的不同形式小结:一览表分析圆锥曲线的极坐标方程非标准位置的圆锥曲线参数方程应用比较 refs abstract 常见平面曲线的方程的不同表示方式 常见曲线的不同形式 下面以平面曲线为对象讨论参数方程通常是对普通方程的补充和增强,曲线的普通方程(直角…...

element使用小结
1、tabel表头文字自定义效果(换行,不同颜色) 换行: // 方法一 <el-table-columnprop"otherCost":label"本期累计\n(元)"> // 通过:label添加\n </el-table-column>.xx .cell {white-space: pre-…...

自动驾驶DCLC 功能规范
目录 1 概述Summary....................................................................................................... 4 1.1 目的Purpose....................................................................................................... 4 1.2 范围Ran…...

LabVIEW中将SMU信号连接到PXI背板触发线
LabVIEW中将SMU信号连接到PXI背板触发线 本文介绍如何将信号从PXI(e)SMU卡路由到PXI(e)机箱上的背板触发线。该过程涉及使用NI-DCPowerVI将SMU信号导出到PXI_TRIG线上。 在继续操作之前,请确保在开发PC上安装了兼容版…...

[蓝桥杯习题]———位运算、判断二进制1个数
⭐Hello!这里是欧_aita的博客。 ⭐今日语录:行动胜过一切。 ⭐个人主页:欧_aita ψ(._. )>⭐个人专栏: 数据结构与算法(内含蓝桥杯习题) MySQL数据库 位运算 位运算位运算的定义简单运用 实战刷题题目思路代码实现声…...

3DCAT为华东师大设计学院打造元宇宙数字虚拟学院
6月11日,华东师范大学设计学院在chi K11美术馆举办了一场别开生面的 2023 年本科毕业设计暨项目实践教学现场演示展。其中,元宇宙数字虚拟学院(一期)的现场发布会引起了现场震撼,吸引了众多观众的目光和参与。 该元宇宙…...

AIGC 3D即将爆发,混合显示成为产业数字化的生产力平台
2023年,大语言模型与生成式AI浪潮席卷全球,以文字和2D图像生成为代表的AIGC正在全面刷新产业数字化。而容易为市场所忽略的是,3D图像生成正在成为下一个AIGC风口,AIGC 3D宇宙即将爆发。所谓AIGC 3D宇宙,即由文本生成3D…...

时间序列预测实战(二十一)PyTorch实现TCN卷积进行时间序列预测(专为新手编写的自研架构)
一、本文介绍 本篇文章给大家带来的是利用我个人编写的架构进行TCN时间序列卷积进行时间序列建模(专门为了时间序列领域新人编写的架构,简单不同于市面上大家用GPT写的代码),包括结果可视化、支持单元预测、多元预测、模型拟合效…...