历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?
ccwgpt 2024-11-03 12:42 129 浏览 0 评论
作者|姜沂(倾寒)
出品|阿里巴巴新零售淘系技术部
导读:自 2014 年苹果发布会发布 Swift 之后, Swift 经过多年迭代,终于达到了 ABI 稳定版本,也意味着 Swift 做为稳定的得语言,值得用在大型 APP, 用来生产环境中。
2019 年 WWDC , 又发布了引起无数 Apple 平台开发者欢呼的框架 SwiftUI, 据非官方消息,SwiftUI 框架孵化于 4 年前,作为苹果全平台的 UI 系统的未来,数十名核心开发者,不准向其他同事和外部披露任何关于此项目的任何信息,于今年释出 Beta 版本后,从方方面面都透出出这是目前最强的移动端声明式 编程框架,没有之一(个人觉得)。在此实战之前作者已经编写了两篇相关的文章。
1、SwiftUI初体验 (点击阅读)
2、系列文章深度解读|SwiftUI 背后那些事儿 (点击阅读)
注: 项目代号为企业内部私有,这里使用 SOT 代指,意为 “Swift on Taobao”。
背景
为了研究 SwiftUI 在业务落地的可能性,我们一直持续关注着 SwiftUI 的发展,但编程这种工作,向来是阅读千编,不如实战一次来的深刻,刚好我们有一个业务场景非常适合,那就是观察稳定性大盘。
整个淘系也有一个用来观察稳定性数据的应用,通常来说数据大盘是比较适合在 PC 浏览器中展示的,我们也在 PC 中使用了多年,但是淘宝 APP 是一个重运营类的 APP, 经常会有一些活动在节假日投放。
但此时值班人员或者相关人员可能在外,有时候可能并未携带电脑,这时候观察稳定性情况就非常窘迫,我们迫切需要一款可以随身携带的APP,用于在紧急时刻观察稳定性问题。
项目耗时
这里先给出时间结论,
整个 SOT APP 耗时 1.3 人力,共 10 个工作日,整个 Swift 代码 约 2800 行。
由于这是一款必须工作在内网下的 APP, 接入内网鉴权没有太多经验,花费不少时间。
整体下来大约有 5天左右的工作量花在调试接口,内网鉴权,原型设计部分,真正花在 SwiftUI 的部分约有 5 天,不得不说效率惊人。
项目设计
原型设计
做一款 APP 的最核心的部分是设计 APP 的功能,熟悉 SOT 的同学,应该知道一般观察稳定性主要是观察数据大盘,聚合列表,分析聚合详情,崩溃分析等比较重要的模块。
落地 SwiftUI 的计划预计 两周,所以 SOT 一期只做做核心常用的部分。功能有了,那么设计怎么办呢?
不要怂,作为 9102 年的程序员,不会做 UI 怎么可以?由于 Mac 平台的 设计软件 如 Keynote 和 Sketch 操作方式,基本和 StoryBoard (只会用代码写UI的同学要回去重新学习下 StoryBoard 了 -)操作非常接近,花了一天时间简单设计了下界面。
这里刻意模仿 App Store的圆角和阴影设计,至于为什么?原因就是负责的设计会让 UI 代码编写变的更有挑战性,如果只是用系统原生的样式,那么碰见的难题就会大大减少,这样的实战到了实际的项目中,碰见的问题还会很多。
事实证明负责的 UI 设计对理解 SwiftUI 非常有价值,单单一个圆角,就花去了 6 个小时开发时间。
数据流管理
SwiftUI 是一个典型的单向数据流得声明式 UI 编程框架, 在 SwiftUI 中 View 只是一个页面的描述部分,SwiftUI 提供了多个数据流管理对象。
@State @Binding @Obserabled ,通过改变这些数据流的值,SwiftUI 系统可以理解重新构建 View Tree, 并根据内部变化的范围,有一层类似 Virtual Dom 的 ViewTree, 由于 View 都是结构体,SwiftUI 每次构建这个 View Tree 都极快,这使得性能有很强的保障。
在实践中也发现了一些Bug,但由于目前 SwiftUI 还在高速变化,这些 Bug 都会在将来的版本中修复,这里就不过多解释了。
State
State 是 SwiftUI 中最常用的 代理属性,通过对代理属性的修改,SwiftUI 内部会自动的重新计算 View的 Body部分,构建 出View Tree。
注意 State 只能在当前 View 的 body 体里面修改,所以 State 的适用场景就是只影响当前 View 内部的变化的操作。
举个实际的例子就是类似下载网络图片的部分,调用方通常提供一个 URL 和 Placeholder Image,在 SwiftUI 中使用 State 即可,因为此时的网络图变化只影响当前 View。
如 APP 选择界面中,图片资源都来源自网络。
示例代码如下 :
Binding
在传统的命令式编程中,GUI 程序中最复杂的部分莫过于状态管理,尤其是多数据同步,一个数据存在于不同的 UI 组成部分,UI 各个部分的变化理论上都有同步,状态量的变多加上异步的操作,会使程序的可读性直线下降,并且伴随着而来的就是 Bug ,并且不敢重构。
SwiftUI 给我们的理念就是 Single source of truth, 简单来说就是单一数据源,单一数据源是个很早就有的名词/方法,但是很多系统并没有给出很好的解决办法,比如习惯 FRP 的同学可能用 RX/RAC 里面的 Singnal 去描述,但是 FRP 晦涩的概念,又使其在项目中的接入成本大大提高。
SwiftUI 给我们的解决办法就是 @Binding 。作者之前尝试自己实现一个 Binding,实现起来就是一个简单的闭包,通过闭包捕获 Source of truth 的数据,同时 SwiftUI 会帮我们自动刷新需要同步的界面。使我们的数据同步变的的非常简单。
实际例子如,系统提供的 Control(可操作的View) 的构造器基本都需要 @Binding 属性,可以自动的同步来自 API 调用方的数据源。
这里举个例子如 项目中的版本选择和日期选择功能,我们需要讲控件选择的值同步给数据源。
struct DateVersionPanel : View { @Binding var version: String @State var input = "" @Binding var date: Date var title: String @State private var showVersionPicker = false @State private var showDatePicker = false var dateFormatter: DateFormatter { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" return formatter } private func showDate() { showDatePicker = true } var body: some View { HStack(alignment: .center) { Text(title) .font(.system(size: 14)) HStack(alignment: .center) { TextField(version.isEmpty ? "不区分版本" : version, text: $input, onEditingChanged: { (changed) in log.debug("TextFieldonEditing: \(changed)") }) { log.debug("TextFielduserName: \(self.version)") self.version = self.input } .font(.system(size: 9)) .padding(.leading, 20) .frame(width: 100, height: 20) NavigationLink(destination: VersionSelectView(version: $version)) { Image("down_arrow") .frame(width: 24, height: 14) .aspectRatio(contentMode: .fill) } .offset(x: -20) } .frame(width: 100, height: 25) .border(Color.grayText, width: 0.5) .padding(.leading, 40) NavigationLink(destination: CalendarView(date: self.$date)) { HStack { Text(dateFormatter.string(from: date) ) .font(.system(size: 9)) .padding(.leading, 10) Image("down_arrow").padding(.trailing, 10) } .frame(width: 100, height: 25) .border(Color.grayText, width: 0.5) .padding(.leading, 40) } } .padding(.bottom, 10) } }
ObservableObject
ObservableObject 在 Xcode11 Beta 4 之前叫 ObjectBinding , 这个类型是一个协议,要求我们实现一个来自 Combine 框架的 Subject Subject 是一个和命令式编程世界交互的桥梁,是一个特殊的 Publisher,SwiftUI 内部会自动的订阅这个 Subject,在 Subject 发送变化时 SwiftUI 会自动刷新数据。
ObservableObject 适用于多个 UI 组成部分同步数据,ObservableObject 取代了,Cocoa 框架基本编程风格 MVC 中控制器的角色,暂时项目中就叫他 ViewModel 吧。
@Published 是 Xcode11 beta5 之后新增的代理属性,此属性如果用在 ObservableObject 内,如果属性发送了变化,会自动触发 ObservableObject 的 objectWillChanged 的Subject变化,自动刷新页面。
同时由于 Combine 框架的支持,多个条件联动变成了一个简单的事情,在 SOT APP 项目中,就非常适合,比如数据大盘,有将近10几个数据状态,任何一个触发,都会导致数据刷新。
class HomeViewModel: ObservableObject { @Published var isCorrectionOn = true @Published var isForce = false @Published var crashType = CrashType.crash @Published var pecision = Pecision.fifith @Published var quota = Quota.count @Published var currentDate = Date() @Published var currentVersion = "" @Published var comDate = Date().lastDay @Published var comVersion = "" @Published var refresh = true @Published var metric: Metric? = nil @Published var trends: [TrendItem] = [] @Published var summary: Summary? = nil var api = SOTAPI() // MARK: - Life Cycle var cancels = [AnyCancellable]() init() { var cancel = $refresh.combineLatest($isForce, $isCorrectionOn) .combineLatest($crashType, $pecision, $quota) .combineLatest($currentDate, $currentVersion) .combineLatest($comVersion, $comDate) .debounce(for: 0.5, scheduler: RunLoop.main) .sink {[weak self](_) in self?.requestMetric() self?.requestTrends() } cancels.append(cancel) cancel = $refresh.sink{[weak self](_) in self?.requestSummary() } cancels.append(cancel) } func requestMetric() {} func requestTrends() {} func requestSummary() {} }
Work with UIKit
由于 SwiftUI 是一个封闭的系统,有时候一些控件还不够丰富,为了满足开发所用,还需要和一些已有的 UIKit的 UIView 混合编程,一方面可以减少迁移的负担,一方面可以增加 SwiftUI 的能力。
在 SOT 项目中,由于日期选择是一个专业的库,这里采用了第三方库,就涉及到于 UIKit 交互, SwiftUI 提供了一套非常简单清晰的标准,可以用在多个平台上交互,并提供一致的表现力。
需要注意的是 UIViewRepresentable 的遵守者,是一个 View 容器,此容器会被创建多次,如果内部有数据源需要通知,需要创建相应的 Coordinator 将当前的容器当做 View 传递进去,由于 View 是结构体。
此时创建的是一个拷贝副本,所以 Coordinator 修改的部分,最好只是 ObservableObject Binding
struct CalendarView : UIViewRepresentable { @Environment(\.presentationMode) var presentationMode @Binding var date: Date init(date: Binding<Date>) { self._date = date } func makeUIView(context: UIViewRepresentableContext<CalendarView>) -> UIView { let view = UIView(frame: UIScreen.main.bounds) view.backgroundColor = .backgroundTheme let height: CGFloat = 300.0 let width = view.frame.size.width let frame = CGRect(x: 0.0, y: 0.0, width: width, height: height) let calendar = FSCalendar(frame: frame) calendar.locale = Locale.init(identifier: "ZH-CN") calendar.delegate = context.coordinator context.coordinator.fsCalendar = calendar calendar.backgroundColor = UIColor.white view.addSubview(calendar) return view } func makeCoordinator() -> CalendarView.Coordinator { Coordinator(self) } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<CalendarView>) { log.debug("Date") context.coordinator.fsCalendar?.select(date) } func dismiss() { presentationMode.wrappedValue.dismiss() } class Coordinator: NSObject, FSCalendarDelegate { var control: CalendarView var date: Date var fsCalendar: FSCalendar? init(_ control: CalendarView) { self.control = control self.date = control.date } func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { self.control.date = date } } }
架构
Combine
在此项目中使用了最基本的 Combine 操作,由于项目一期主要是为了探索 SwiftUI ,所以并未对架构模式做精细的设计,可以观察到,ViewModel,内部还是有订阅,发送网络请求,最后同步数据的操作,这种编码方式,还是典型的命令式编程风格,此部分会在项目二期逐渐探索中修改为响应式风格。
Redux/Flux
SwiftUI 是一个单向数据流框架,在此之前,大前端已经有 React, Flutter , Reactive Native,等比较流行的框架。在这些单向数据流得框架下,Redux 作为一种比较流行的状态管理的架构风格,已经经过多方面的验证,SwiftUI 对于Redux也是比较适用的。
Redux 的基本思想核步骤是:
1、整个页面甚至 APP 是一个巨大的状态机,有一个状态存储 Store ,在某个时刻处于某种状态。
2、状态在页面表达中是一个简单的树型结构,在 SwiftUI,对应的 就是 View Tree。
3、View 操作不能直接修改状态,只能通过发送 Action, 间接改变 Store。
4、Reducer 通过 Action 加上 oldState 获取 newSatete。简单来说就是 State = f(action+oldState)。
附上一份 阮一峰的Redux入门教程的示例图:
这套风格在前端大型项目中已经了验证,可以比较清晰的表达用户事件交互和状态管理。
目前由于 SwiftUI 中 ViewCtonroller的消失,加上方便的 ObserableObject 和 EmviromentObject 。
SOT 项目一期暂未采用,在二期项目中会探索合适的架构设计。
项目总结
此项目在短短的 10 个工作日内就能完成,不得不说 SwiftUI 的开发效率真的惊人,虽然目前还有一些 Bug ,但是相信在未来,SwiftUI 会是 Apple 平台 UI 布局的解决办法,关于 SwiftUI 如何在淘系落地业务,还在持续探索中。
目前此项目已在集团内部开源。
作者:姜沂(倾寒) | 阿里巴巴新零售淘系技术部
相关推荐
- 十分钟让你学会LNMP架构负载均衡(impala负载均衡)
-
业务架构、应用架构、数据架构和技术架构一、几个基本概念1、pv值pv值(pageviews):页面的浏览量概念:一个网站的所有页面,在一天内,被浏览的总次数。(大型网站通常是上千万的级别)2、u...
- AGV仓储机器人调度系统架构(agv物流机器人)
-
系统架构层次划分采用分层模块化设计,分为以下五层:1.1用户接口层功能:提供人机交互界面(Web/桌面端),支持任务下发、实时监控、数据可视化和报警管理。模块:任务管理面板:接收订单(如拣货、...
- 远程热部署在美团的落地实践(远程热点是什么意思)
-
Sonic是美团内部研发设计的一款用于热部署的IDEA插件,本文其实现原理及落地的一些技术细节。在阅读本文之前,建议大家先熟悉一下Spring源码、SpringMVC源码、SpringBoot...
- springboot搭建xxl-job(分布式任务调度系统)
-
一、部署xxl-job服务端下载xxl-job源码:https://gitee.com/xuxueli0323/xxl-job二、导入项目、创建xxl_job数据库、修改配置文件为自己的数据库三、启动...
- 大模型:使用vLLM和Ray分布式部署推理应用
-
一、vLLM:面向大模型的高效推理框架1.核心特点专为推理优化:专注于大模型(如GPT-3、LLaMA)的高吞吐量、低延迟推理。关键技术:PagedAttention:类似操作系统内存分页管理,将K...
- 国产开源之光【分布式工作流调度系统】:DolphinScheduler
-
DolphinScheduler是一个开源的分布式工作流调度系统,旨在帮助用户以可靠、高效和可扩展的方式管理和调度大规模的数据处理工作流。它支持以图形化方式定义和管理工作流,提供了丰富的调度功能和监控...
- 简单可靠高效的分布式任务队列系统
-
#记录我的2024#大家好,又见面了,我是GitHub精选君!背景介绍在系统访问量逐渐增大,高并发、分布式系统成为了企业技术架构升级的必由之路。在这样的背景下,异步任务队列扮演着至关重要的角色,...
- 虚拟服务器之间如何分布式运行?(虚拟服务器部署)
-
在云计算和虚拟化技术快速发展的今天,传统“单机单任务”的服务器架构早已难以满足现代业务对高并发、高可用、弹性伸缩和容错容灾的严苛要求。分布式系统应运而生,并成为支撑各类互联网平台、企业信息系统和A...
- 一文掌握 XXL-Job 的 6 大核心组件
-
XXL-Job是一个分布式任务调度平台,其核心组件主要包括以下部分,各组件相互协作实现高效的任务调度与管理:1.调度注册中心(RegistryCenter)作用:负责管理调度器(Schedule...
- 京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
-
京东大佬问我,SpringBoot中如何做延迟队列?单机如何做?分布式如何做呢?并给出案例与代码分析。嗯,用户问的是在SpringBoot中如何实现延迟队列,单机和分布式环境下分别怎么做。这个问题其实...
- 企业级项目组件选型(一)分布式任务调度平台
-
官网地址:https://www.xuxueli.com/xxl-job/能力介绍架构图安全性为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;调度中心和执...
- python多进程的分布式任务调度应用场景及示例
-
多进程的分布式任务调度可以应用于以下场景:分布式爬虫:importmultiprocessingimportrequestsdefcrawl(url):response=re...
- SpringBoot整合ElasticJob实现分布式任务调度
-
介绍ElasticJob是面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目ElasticJob-Lite和ElasticJob-Cloud组成。它通过弹性调度、资源管控、...
- 分布式可视化 DAG 任务调度系统 Taier 的整体流程分析
-
Taier作为袋鼠云的开源项目之一,是一个分布式可视化的DAG任务调度系统。旨在降低ETL开发成本,提高大数据平台稳定性,让大数据开发人员可以在Taier直接进行业务逻辑的开发,而不用关...
- SpringBoot任务调度:@Scheduled与TaskExecutor全面解析
-
一、任务调度基础概念1.1什么是任务调度任务调度是指按照预定的时间计划或特定条件自动执行任务的过程。在现代应用开发中,任务调度扮演着至关重要的角色,它使得开发者能够自动化处理周期性任务、定时任务和异...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)