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

Swift知识点---RxSwift学习

1. 什么是RxSwift

RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发、维护

RxSwift的目的是:让数据/事件流 和 异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程

RxSwift本质上还是观察者模式,并且是一个响应式的,并且可以序列化

观察者模式

观察者模式包括:KVO、通知等

爸爸妈妈照看观察宝宝
其中,宝贝就是被观察者
爸爸妈妈就是观察者,或者说是订阅者
只要被观察者(宝宝)发出来某些事件,比如哭声、叫声,则被称为事件,通知到订阅者
此时,订阅者就可以做响应的工作

RxSwift做了什么?

RxSwift把我们程序中的每一个操作都看成一个事件
比如一个TextField中的文本改变、一个按钮被点击、一个网络请求结束等
每一个事件源就可以看成一个管道,也就是sequence

比如,TextField,当我们改变里面的文本的时候,这个TextField就会不断的发出事件
从他的sequence中不断流出,我们只需要监听这个sequence,每流出一个事件就做相应的处理

同理,Button也是一个sequence,每点击一次就流出一个事件

理解Observable和Observer

在这里插入图片描述


2. RxSwift简单体验

  • RxSwift监听按钮的点击
  • RxSwift监听UITextField的文字改变
  • RxSwift改变Label中的文字
  • RxSwift监听对象属性改变
  • RxSwift监听UIScrollView的滚动
//导入RxSwift
import RxSwift
import RxCocoa

按钮监听

原始方法:
button.addTarget(self, action: #selector(button1Click), for: .touchUpInside)

使用RxSwift的方法:

self.button.rx.tap.subscribe { (event: Event<Void>) inprint(event)
}

上述方法会有一个标黄的警告:Result of call to ‘subscribe’ is unused
修改方法:

private lazy var disposeBag: DisposeBag = DisposeBag()self.button.rx.tap.subscribe { (event: Event<Void>) inprint(event)
}.disposed(by: disposeBag)```

监听UITextField文字的改变

方法一:传统方法

方法二:
self.view.addSubview(self.textField)
self.textField.frame = CGRect(x: 200, y: 300, width: 100, height: 40)
self.textField.rx.text.subscribe { (event: Event<String?>) inprint(event.element)//获取信息
}.disposed(by: disposeBag)

这种方法,获取的event有两个Optional包裹着,使用起来不方便

方法三:
self.textField.rx.text.subscribe { (myString: String?) inprint(myString)//获取的是Optional类型
} onError: { error inprint(error)
} onCompleted: {print("onCompleted")
} onDisposed: {print("onDisposed")
}.disposed(by: disposeBag)

以上可以简化:

self.textField.rx.text.subscribe { (myString: String?) inprint(myString)//获取的是Optional类型
}.disposed(by: disposeBag)

监听Label中的文字

UITextField文字输入,然后Label显示输入的文字

方法一:
self.textField.rx.text.subscribe { (myString: String?) inprint(myString)self.myTitleLabel.text = myString
}.disposed(by: disposeBag)self.view.addSubview(myTitleLabel)
myTitleLabel.frame = CGRect(x: 100, y: 210, width: 200, height: 40)
方法二:
self.textField.rx.text.bind(to: myTitleLabel.rx.text).disposed(by: disposeBag)self.view.addSubview(myTitleLabel)
myTitleLabel.frame = CGRect(x: 100, y: 210, width: 200, height: 40)

RxSwift的KVO

self.view.addSubview(myTitleLabel)
myTitleLabel.frame = CGRect(x: 100, y: 210, width: 200, height: 40)myTitleLabel.rx.observe(String.self, "text").subscribe { (str: String?) inprint(str)
}.disposed(by: disposeBag)

RxSwift监听UIScrollView的滚动

self.view.addSubview(scrollView)
scrollView.frame = CGRect(x: 100, y: 280, width: 200, height: 150)
scrollView.rx.contentOffset.subscribe { (point: CGPoint) inprint(point)
}.disposed(by: disposeBag)

3. RxSwift常见操作

Never、Empty、Just、Of、From、Create、Range、RepeatElement

  • Never,什么都不执行
///never,啥事没有
let observableNever = Observable<String>.never()
observableNever.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)
  • Empty创建一个空的sequence,只能发出一个completed事件
 //只执行completed
let observableEmpty = Observable<String>.empty()
observableEmpty.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)打印结果:        
completed
  • Just是创建一个sequence,只能发出一种特定的事件,并且能正常结束

也就是,特定事件可以监听,complete事件可以监听

///Just:只执行特定类型+complete
let observableJust = Observable.just(123)//Int类型
observableJust.subscribe { (event: Event<Int>) in//Event<Int>相对应print(event)
}.disposed(by: disposeBag)打印结果:     
next(123)
completed
  • Of可以执行特定类型+complete
  • 创建一个sequence,可以发出多个事件信号
///Of: 可以执行特定类型多个事件+complete
let observableOf = Observable.of("1", "2", "3", "6")
observableOf.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)打印结果:          
next(1)
next(2)
next(3)
next(6)
completed
  • From从数组中创建sequence
///From: 可以执行数组+complete
let observableFrom = Observable.from(["123", "234", "345"])
observableFrom.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)打印结果:
next(123)
next(234)
next(345)
completed
  • Create可以自定义可观察的sequence
  • Create操作符传入一个观察者observer,然后调用observer的onNext,onCompleted和onError方法,返回一个可观察的observable序列
///Create: 可以自定义观察事件+complete
let observableCreate = createObservableFunc()
observableCreate.subscribe { (event: Event<Any>) inprint(event)
}.disposed(by: disposeBag)///创建自定义的createObservable
private func createObservableFunc() -> Observable<Any> {return Observable.create { (observer: AnyObserver<Any>) inobserver.onNext("123")observer.onNext("321")//注意:Disposables,带sreturn Disposables.create()}
}
打印结果:
next(123)
next(321)

如果想要completed,则需要加上:observer.onCompleted()

  • Range创建一个sequence,会发出这个范围中的从开始到结束的所有事件
///Range: 可以执行某个范围内的
let observableRange = Observable.range(start: 3, count: 3)
observableRange.subscribe { (event: Event<Int>) inprint(event)
}.disposed(by: disposeBag)打印结果:
next(3)
next(4)
next(5)
completed
  • RepeatElement重复执行某个事件
///RepeatElement: 重复执行某个事件
let observableRepeatElement = Observable.repeatElement("2")
observableRepeatElement.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)

执行以上代码,恭喜你,程序干崩溃了= =

可以加上take(count)

///RepeatElement: 重复执行某个事件
let observableRepeatElement = Observable.repeatElement("2")
observableRepeatElement.take(3).subscribe { (event: Event<String>) inprint(event)}.disposed(by: disposeBag)打印结果:
next(2)
next(2)
next(2)
completed

4. RxSwift中Subjects

Subjects是什么?

Subject是Observable和Observer之间的桥梁
一个Subject既是一个Observable,也是一个Observer
即可以监听事件,也可以发出事件

Observable:监听事件
Observer:发出事件

PublishSubject、ReplaySubject、BehaviorSubject、BehaviorRelay

PublishSubject

当订阅PublishSubject的时候,只能接收订阅他之后发生的事件
subject.onNext()发出onNext事件,对应的还有OnError()和onCompleted()事件

let publishSub = PublishSubject<String>()
//不接收
publishSub.onNext("0")//订阅事件
publishSub.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)//只能接收订阅后的事件
publishSub.onNext("1")
publishSub.onNext("2")
publishSub.onNext("3")
publishSub.onCompleted()打印结果:
next(1)
next(2)
next(3)
completed

ReplaySubject

当你订阅ReplaySubject的时候,你可以接收到订阅他之后的事件
但也可以接受订阅他之前发出的事件,接受前面几个事件,取决与bufferSize的大小

//bufferSize决定订阅前接收几个。订阅后的都可以接收到
let replaySbu = ReplaySubject<String>.create(bufferSize: 2)
replaySbu.onNext("1-0")
replaySbu.onNext("1-1")
replaySbu.onNext("1-2")
//订阅事件
replaySbu.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)
replaySbu.onNext("1-3")
replaySbu.onNext("1-4")
replaySbu.onNext("1-5")
replaySbu.onCompleted()打印结果:
next(1-1)
next(1-2)
next(1-3)
next(1-4)
next(1-5)
completed

BehaviorSubject

BehaviorSubject会接受到订阅之前的最后一个事件

//behaviorSbu接收订阅前最后一个。订阅后的都可以接收到
let behaviorSbu = BehaviorSubject(value: "1")
behaviorSbu.onNext("2-0")
behaviorSbu.onNext("2-1")
behaviorSbu.onNext("2-2")
//订阅事件
behaviorSbu.subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)
behaviorSbu.onNext("2-3")
behaviorSbu.onNext("2-4")
behaviorSbu.onNext("2-5")
behaviorSbu.onCompleted()打印结果:
next(2-2)
next(2-3)
next(2-4)
next(2-5)
completed

BehaviorRelay

behaviorRelay是BehaviorSubject的一个包装箱
使用的时候,需要调用asObservable()拆箱,里面的value是一个BehaviorSubject
如果Variable准备发出事件,不需要onNext这种方式,而是直接修改对象的value即可

let behaviorRelay = BehaviorRelay(value: "3-1")
behaviorRelay.accept("3-2")
behaviorRelay.accept("3-3")
behaviorRelay.asObservable().subscribe { (event: Event<String>) inprint(event)
}.disposed(by: disposeBag)
behaviorRelay.accept("3-4")
behaviorRelay.accept("3-5")打印结果:
next(3-3)
next(3-4)
next(3-5)

RxSwift在UITableView的使用

方法一:

Model:

struct HeroModel: Codable {var name: Stringvar age: Stringvar description: Stringenum CodingKeys: String, CodingKey {case namecase agecase description = "describe"}
}

ViewModel:

class HeroViewModel {var herosObserrable: BehaviorRelay<[HeroModel]> = {var herosArray: [HeroModel] = [HeroModel]()guardlet path = Bundle.main.path(forResource: "RxSwiftData", ofType: "json"),let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path))else {print("Error loading JSON file")return BehaviorRelay(value: [])}do {let decoder = JSONDecoder()herosArray = try decoder.decode([HeroModel].self, from: jsonData)for hero in herosArray {print(hero)}}catch {print("Error parsing JSON: \(error)")}return BehaviorRelay(value: herosArray)}()
}
extension ViewController {fileprivate func loadData(){//获取VMlet heroVM = HeroViewModel()//订阅VMheroVM.herosObserrable.asObservable().subscribe { (heros: [HeroModel]) inself.herosArray.removeAll()self.herosArray = herosself.myTableView.reloadData()}.disposed(by: disposeBag)}
}extension ViewController: UITableViewDelegate, UITableViewDataSource {func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return herosArray.count}func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as? MyTableViewCell else {print("Error: Could not dequeue cell as MyTableViewCell")return UITableViewCell()}cell.nameLabel.text = herosArray[indexPath.row].namecell.ageLabel.text = herosArray[indexPath.row].agecell.desLabel.text = herosArray[indexPath.row].descriptionreturn cell}
}private lazy var myTableView: UITableView = {let myTableView = UITableView()myTableView.delegate = selfmyTableView.dataSource = selfmyTableView.register(MyTableViewCell.self, forCellReuseIdentifier: "MyTableViewCell")return myTableView
}()

方法二:

Model、ViewModel同上

extension ViewController {fileprivate func loadData(){let heroVM = HeroViewModel()//直接在这,将监听的数据,直接赋值heroVM.herosObserrable.asObservable().bind(to: myTableView.rx.items(cellIdentifier: "MyTableViewCell", cellType: MyTableViewCell.self)){(row, hero, cell) incell.nameLabel.text = hero.namecell.ageLabel.text = hero.agecell.desLabel.text = hero.description}.disposed(by: disposeBag)}
}

相关文章:

Swift知识点---RxSwift学习

1. 什么是RxSwift RxSwift是Swift函数响应式编程的一个开源库&#xff0c;由Github的ReactiveX组织开发、维护 RxSwift的目的是&#xff1a;让数据/事件流 和 异步任务能够更方便的序列化处理&#xff0c;能够使用Swift进行响应式编程 RxSwift本质上还是观察者模式&#xff…...

驾驭不断发展的人工智能世界

从很多方面来看&#xff0c;历史似乎正在重演。许多企业正争相采用生成式人工智能 (Gen AI)&#xff0c;就像它们争相采用云计算一样&#xff0c;原因也是一样的&#xff1a;效率、成本节约和竞争优势。 然而&#xff0c;与云一样&#xff0c;GenAI 仍是一项发展中的技术&…...

冒泡排序——基于Java的实现

简介 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;适用于小规模数据集。其基本思想是通过重复遍历待排序的数组&#xff0c;比较相邻的元素并交换它们的位置&#xff0c;以此将较大的元素逐步“冒泡”到数组的末尾。算法的名称源于其运行过程…...

Mendix 创客访谈录|Mendix赋能汽车零部件行业:重塑架构,加速实践与数字化转型

在当前快速发展的技术时代&#xff0c;汽车行业正经历着前所未有的数字化转型。全球领先的汽车零配件制造商面临着如何利用最新的数字技术优化其制造车间管理的挑战。从设备主数据管理到生产执行工单管理&#xff0c;再到实时监控产量及能耗&#xff0c;需要一个灵活、快速且高…...

船舶机械设备5G智能工厂物联数字孪生平台,推进制造业数字化转型

船舶机械设备5G智能工厂物联数字孪生平台&#xff0c;推进制造业数字化转型。在当今数字化浪潮推动下&#xff0c;船舶制造业正经历着前所未有的变革。为了应对市场的快速变化&#xff0c;提升生产效率&#xff0c;降低成本&#xff0c;并增强国际竞争力&#xff0c;船舶机械设…...

什么是jsonp请求

JSONP&#xff08;JSON with Padding&#xff09;是一种解决跨域请求问题的技术。它允许网页从不同的域名请求数据&#xff0c;而不受同源策略的限制。JSONP 通过动态创建 script 标签来实现跨域请求&#xff0c;因为 script 标签不受同源策略的限制。 一、工作原理 客户端&a…...

【C++】STL容器详解【上】

目录 一、STL基本概念 二、STL的六大组件 三、string容器常用操作 3.1 string 容器的基本概念 3.2 string 容器常用操作 3.2.1 string 构造函数 3.2.2 string基本赋值操作 3.2.3 string存取字符操作 3.2.4 string拼接字符操作 3.2.5 string查找和替换 3.2.6 string比…...

助贷行业的三大严峻挑战:贷款中介公司转型债务重组业务

大家是否察觉到一种趋势&#xff1f;现如今&#xff0c;众多贷款辅助服务机构与专注于债务再构的公司之间形成了紧密的“联动”。有的选择将获取的贷款需求转介给债务重组方&#xff0c;有的则直接下场&#xff0c;动用自身资本参与债务重组业务。这一现象背后&#xff0c;究竟…...

力扣第42题 接雨水

前言 记录一下刷题历程 力扣第42题 接雨水 接雨水 原题目&#xff1a;给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&…...

轻松录制每一刻:探索2024年免费高清录屏应用

你不会还在用一些社交工具来录屏吧&#xff1f;现在的市面上有不少免费录屏的软件了。别看如软件是免费的&#xff0c;它的功能比起社交工具的录屏功能来说全面的多。这次我就分享几款我用过的录屏工具。 1.福晰录屏大师 链接直达&#xff1a;https://www.foxitsoftware.cn/R…...

【小沐学OpenGL】Ubuntu环境下glfw的安装和使用

文章目录 1、简介1.1 OpenGL简介1.2 glfw简介 2、安装glfw2.1 直接命令二进制安装2.2 源码安装 3、测试glfw3.1 测试1&#xff0c;glfwglew3.2 测试2&#xff0c;glfwglad3.3 测试3 结语 1、简介 1.1 OpenGL简介 OpenGL作为图形界的工业标准&#xff0c;其仅仅定义了一组2D和…...

[数据集][目标检测]汽油检泄漏检测数据集VOC+YOLO格式237张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;237 标注数量(xml文件个数)&#xff1a;237 标注数量(txt文件个数)&#xff1a;237 标注类别…...

图文解析保姆级教程:Postman专业接口测试工具的安装和基本使用

文章目录 1. 引入2. 介绍3. 安装4. 使用 此教程摘选自我的笔记&#xff1a;黑马JavaWeb开发笔记16——请求&#xff08;postman、简单参数、实体参数、RequestParam映射&#xff09;想要详细了解更多有关请求各种参数介绍的知识可以移步此篇笔记。 1. 引入 在当前最为主流的开…...

jenkins配置流水线

新建任务&#xff0c;随便选一个名字&#xff0c;选中流水线 配置git的用户名和密码&#xff0c;记录ID&#xff0c;后面配置流水线的时候用。 pipeline {agent anystages {stage(stop app){steps {script {def remote [:]//配置服务地址&#xff0c;用户名和密码remote.na…...

SQL 编程基础

SQL&#xff08;结构化查询语言&#xff09;广泛应用于数据库操作&#xff0c;是每个程序员都需要掌握的技能之一。这篇文章将带你从基础入门&#xff0c;了解SQL编程中的常量、变量及流程控制语句。我们将采用简单易懂的语言&#xff0c;结合实际示例&#xff0c;帮助你轻松理…...

sql 中名字 不可以 包含 mysql中 具有 特定意义 的单词

这种sql执行不报错 这种sql执行报错 所以sql中名字不可以使用mysql中具有特定意义的单词 以此文章作为警告&#xff0c;我下次起名字不可以使用 mysql中具有特殊意义的字符 就因为这个导致我搞了一个多小时&#xff0c;急死我了&#xff0c;周五就要前后端联调了。下次千万不…...

分布式部署①

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 1. 需要部署的服务 Nacos 理论上,应…...

开源可视化大屏superset Docker环境部署

superset 开源可视化大屏Docker环境部署 前言 superset是俄罗斯开源的一款可视化大屏&#xff0c;用于数据可视化探索&#xff0c;含有丰富的图表组件&#xff0c;可以支持接入各种数据源。 接触superset就是想体验下可视化大屏功能&#xff0c;想最快速度安装成功&#xff…...

tomato靶场通关攻略

1.御剑2014找到IP地址 2.dirb扫描目录 3.再次详细扫描目录 4.访问找到的目录文件 进入antibots中 5.搜寻一会再info.php里面发现有东西 6.这个地方貌似可以进行利用 7.查看源代码发现包含include文件上传漏洞 8.网址后面跟?image../../../../../../../etc/passwd 9.既然可以查…...

【Spring Boot 3】【Web】处理跨域资源共享 CORS

【Spring Boot 3】【Web】处理跨域资源共享 CORS 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术…...

如何高效处理大规模地图数据:Google Maps Services Python 并发处理终极指南

如何高效处理大规模地图数据&#xff1a;Google Maps Services Python 并发处理终极指南 【免费下载链接】google-maps-services-python Python client library for Google Maps API Web Services 项目地址: https://gitcode.com/gh_mirrors/go/google-maps-services-python …...

Qwen3-ForcedAligner-0.6B在字幕制作中的落地应用:SRT自动导出全流程

Qwen3-ForcedAligner-0.6B在字幕制作中的落地应用&#xff1a;SRT自动导出全流程 1. 引言&#xff1a;告别手动打轴&#xff0c;让字幕制作快10倍 如果你做过视频字幕&#xff0c;一定体会过手动打轴的痛苦。一集45分钟的视频&#xff0c;台词稿早就准备好了&#xff0c;但你…...

Obsidian图像转换:提升笔记效率的格式优化解决方案

Obsidian图像转换&#xff1a;提升笔记效率的格式优化解决方案 【免费下载链接】obsidian-image-converter ⚡️ Convert, compress, resize, annotate, markup, draw, crop, rotate, flip, align images directly in Obsidian. Drag-resize, rename with variables, batch pro…...

不止于超市:用QGIS缓冲区+叠置分析,为你的奶茶店、自习室找个好位置

从奶茶店到自习室&#xff1a;QGIS空间分析赋能小微商业选址决策 走在街头&#xff0c;你是否好奇为什么某些奶茶店总是门庭若市&#xff0c;而几步之隔的同类店铺却冷冷清清&#xff1f;商业选址从来不是简单的"地段好"三个字能概括的。对于资金有限的小微创业者来说…...

2024通信工程师初级备考指南:综合能力与专业实务核心考点解析

1. 2024通信工程师初级考试概况 2024年通信工程师初级资格考试定于9月28日举行&#xff0c;采用机考形式&#xff0c;考试时间为上午8:30至12:30&#xff0c;总时长4小时。这个考试分为两个科目&#xff1a;《通信专业综合能力》和《通信专业实务》&#xff0c;两科连续考试&am…...

Llama-3.2V-11B-cot与Dify集成:零代码构建企业AI智能体

Llama-3.2V-11B-cot与Dify集成&#xff1a;零代码构建企业AI智能体 最近和几个做企业服务的朋友聊天&#xff0c;大家普遍有个感觉&#xff1a;现在AI模型能力越来越强&#xff0c;但真要把它们用起来&#xff0c;门槛还是有点高。特别是对于业务部门的人来说&#xff0c;看着…...

从攻到防:实战演练基于Wireshark与Snort的DoS攻击检测

1. 拒绝服务攻击初探&#xff1a;原理与危害剖析 想象一下周末去热门餐厅吃饭的场景。当所有座位都被占满&#xff0c;门口还不断涌入大量"假顾客"时&#xff0c;真正的食客就会被挡在门外——这就是拒绝服务攻击&#xff08;DoS&#xff09;的生动写照。作为网络安…...

实战演练:基于快马平台生成学生成绩排名系统,掌握排序算法应用

最近在做一个学生成绩管理系统的实战项目&#xff0c;其中排序功能是核心模块。通过这个项目&#xff0c;我深刻体会到排序算法在实际应用中的重要性。下面分享一下我的实现思路和经验总结。 学生类设计 首先需要定义一个学生类&#xff0c;包含学号、姓名、各科成绩和总成绩等…...

Wan2.2-T2V-A5B常见错误排查:运行失败、生成卡顿的解决方法

Wan2.2-T2V-A5B常见错误排查&#xff1a;运行失败、生成卡顿的解决方法 1. 问题概述与快速诊断 Wan2.2-T2V-A5B作为一款轻量级文本到视频生成模型&#xff0c;虽然在资源消耗和响应速度上具有优势&#xff0c;但在实际使用过程中仍可能遇到运行失败或生成卡顿的问题。这些问题…...

Guohua Diffusion 创意编程:用Processing可视化交互控制图像生成

Guohua Diffusion 创意编程&#xff1a;用Processing可视化交互控制图像生成 你有没有想过&#xff0c;自己随手画的一条线、选择的一个颜色&#xff0c;能立刻变成一幅由AI生成的完整画作&#xff1f;这听起来像是科幻电影里的场景&#xff0c;但现在&#xff0c;通过一点创意…...