历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?

 

作者|姜沂(倾寒) 
出品|阿里巴巴新零售淘系技术部

导读:自 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 选择界面中,图片资源都来源自网络。

示例代码如下 :

struct NetworkImage: SwiftUI.View {var urlPath: String?
var placeHodlerImage: UIImage
init(url path: String?, placeHolder: String) {
self.urlPath = path
self.placeHodlerImage = UIImage(named: placeHolder)!.withRenderingMode(.alwaysOriginal)}@State var downLoadedImage: UIImage? = nil
var body: some SwiftUI.View {
Image(uiImage: downLoadedImage ?? placeHodlerImage).resizable().aspectRatio(contentMode: .fill).onAppear(perform: download)}
func download() {
if let _ = downLoadedImage {
return}
_ = urlPath.flatMap(URL.init(string:)).map {
ImageDownloader.default.downloadImage(with: $0) { result in
switch result {
case .success(let value):
self.downLoadedImage = value.image.withRenderingMode(.alwaysOriginal)
case .failure(let error):log.debug(error)}}}}
}

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 = falsevar 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) inlog.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? = nilvar api = SOTAPI()// MARK: - Life Cyclevar 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](_) inself?.requestMetric()self?.requestTrends()}cancels.append(cancel)cancel = $refresh.sink{[weak self](_) inself?.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: Dateinit(date: Binding<Date>) {
self._date = date}func makeUIView(context: UIViewRepresentableContext<CalendarView>) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)view.backgroundColor = .backgroundThemelet 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.coordinatorcontext.coordinator.fsCalendar = calendarcalendar.backgroundColor = UIColor.whiteview.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 如何在淘系落地业务,还在持续探索中。

目前此项目已在集团内部开源。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Istio从懵圈到熟练 – 二分之一活的微服务

Istio is the future&#xff01;基本上&#xff0c;我相信对云原生技术趋势有些微判断的同学&#xff0c;都会有这个觉悟。其背后的逻辑其实是比较简单的&#xff1a;当容器集群&#xff0c;特别是K8S成为事实上的标准之后&#xff0c;应用必然会不断的复杂化&#xff0c;服务…

数据结构与算法、讲解、动态规划一脸懵?看完之后轻松掌握!

来源 | 昊天码字责编 | Carol封图 | CSDN 付费下载于视觉中国碰到动态规划问题摸不着头脑&#xff1f;总结不出动态规划的类型&#xff1f;有多少人曾经历过这种迷茫与无助&#xff1f;看完本文&#xff0c;让你一脚迈进动态规划的大门。我们在用递归求解问题的过程中&#xff…

搜索场景下的智能推荐演变之路

摘要&#xff1a;传统的推荐手段主要还是深度挖掘用户行为和内容本身相似性的价值&#xff0c;包括但不限于协同过滤&#xff0c;内容表征向量召回&#xff0c;以及各式各样的点击率预估模型&#xff0c;然后这样的推荐行为缺乏内在的逻辑性和可解释性&#xff0c;有一种知其然…

调查了 17,000 多位程序员,当前的云原生开发现状究竟如何?

整理 | 弯月&#xff0c;责编 | 郭芮头图 | CSDN 下载自东方IC出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;容器的标准化使用改变了软件的开发方式&#xff0c;我们迎来了开发运维的时代&#xff0c;基于云原生的开发能够帮助我们构建更灵活、更强大的应用程序。…

阿里研究员:测试稳定性三板斧,我怎么用?

阿里妹导读&#xff1a;如何治理测试稳定性问题&#xff1f;很多人会说&#xff1a;环境、流程管控、监控、工具化、加机器、专人负责、等等。这些都是对的。不过这些都是解决方案层面的&#xff0c;而不是方法论和理论体系层面的。今天&#xff0c;阿里研究员郑子颖来说说测试…

阿里架构总监一次讲透中台架构,13页PPT精华详解,建议收藏!

本文整理了阿里几位技术专家&#xff0c;如架构总监 谢纯良&#xff0c;中间件技术专家 玄难等几位大牛&#xff0c;关于中台架构的几次分享内容&#xff0c;将业务中台形态、中台全局架构、业务中台化、中台架构图、中台建设方法论、中台组织架构、企业中台建设实施步骤等总共…

Redis 6.0 的客户端缓存是怎么肥事?一文带你了解!

来源 | 程序员历小冰责编 | Carol封图 | CSDN 付费下载于视觉中国近日 Redis 6.0.0 GA 版本发布&#xff0c;这是 Redis 历史上最大的一次版本更新&#xff0c;包括了客户端缓存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等诸多更新。我们今天就依次聊…

AI时代,你的职业会是?99%的人都无法直面!

在我10岁的时候&#xff0c;算命先生曾对说我30岁时我会每天与八阿哥玩在一起。 当时懵懂的我一脸茫然&#xff0c;想着谁是我的八阿哥&#xff0c;却在30岁的这一年意识到自己确实日以继夜的与八阿哥在一起。 曾经&#xff0c;我们也担心自己未来的工作岗位是否会被人工智能给…

Java 12 新特性概述

Java 12 已如期于 3 月 19 日正式发布&#xff0c;此次更新是 Java 11 这一长期支持版本发布之后的一次常规更新&#xff0c;截至目前&#xff0c;Java 半年为发布周期&#xff0c;并且不会跳票承诺的发布模式&#xff0c;已经成功运行一年多了。通过这样的方式&#xff0c;Jav…

5G +边缘计算,优酷如何做云渲染?

作者| 阿里文娱高级技术专家 伊耆责编 | 屠敏头图 | CSDN 下载自东方 IC出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;当5G来了&#xff0c;视频还是平面的影像吗&#xff0c;只能静静观看吗&#xff1f;一定不是&#xff01;现在&#xff0c;你可以像玩游戏一样…

不做会死!互联网时代的企业必定都要实现中台

AI 前线导读&#xff1a; 自 2018 年底以来&#xff0c;伴随着阿里、腾讯、百度、京东等一众互联网巨头的大规模组织架构调整&#xff0c;“中台”的热度陡然攀升。一时间&#xff0c;各大互联网公司纷纷开始跟随建设中台。中台的概念是被阿里带火的&#xff0c;2015 年&#x…

包机制。。

包机制 为了更好的组织类&#xff0c;java提供了包机制&#xff0c;用于区别类的命名空间//本质就是文件夹 包语法格式 package pkj[.pkg[.pkg3...]];一般利用公司域名倒置作为包名&#xff1a;com.boss.xxx 导入包语法 import package1[.package2...].(classname|*);尽量不要…

ETL异构数据源Datax_使用querySql_08

使用说明 当用户配置了这一项之后&#xff0c;DataX系统就会忽略table&#xff0c;column 这些配置型&#xff0c;直接使用这个配置项的内容对数据进行筛选&#xff0c;例 如需要进行多表join后同步数据&#xff0c;使用select a,b from table_a join table_b on table_a.id t…

我被“非结构化数据包围了”,请求支援!

阿里妹导读&#xff1a;非结构化数据的内容占据了当前数据海洋的80%。换句话来说&#xff0c;就是我们都被“非结构化数据”包围了。由于非结构化数据的信息量和信息的重要程度很难被界定&#xff0c;因此对非结构化数据的使用成为了难点。如果说结构化数据用详实的方式记录了企…

82年 AI程序员征婚启示火了!年薪百万,女生神回复

最近在某社区&#xff0c;一则程序员征婚启示火了&#xff01;很多女生在评论区表示“全中”&#xff0c;想交流看看。然后评论区就炸了&#xff0c;有人恶意说yp&#xff0c;有人说看中了楼主的钱。笔者一翻&#xff0c;发现楼主果然无意中透露了百万年薪收入&#xff0c;虽然…

AWS 专家教你使用 Spring Boot 和 DJL ,轻松搭建企业级机器学习微服务!

作者 | Qing Lan&#xff0c;Mikhail Shapirov责编 | Carol封图 | CSDN 下载自视觉中国出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;许多AWS云服务的用户&#xff0c;无论是初创企业还是大公司&#xff0c;都在逐步将机器学习 (ML) 和深度学习 (DL) 任务…

【从入门到放弃-ZooKeeper】ZooKeeper入门

前言 ZooKeeper是一个分布式服务协调框架&#xff0c;可以用来维护分布式配置信息、服务注册中心、实现分布式锁等。在Hbase、Hadoop、kafka等项目中都有广泛的应用。随着分布式、微服务的普及&#xff0c;ZooKeeper已经成为我们日常开发工作中无法绕过的一环&#xff0c;本文…

ln: failed to create symbolic link ‘/usr/bin/mysql’: File exists

问题描述&#xff1a; ln -s /usr/local/mysql/bin/mysql /usr/bin 在centos7进行软链接设置的时候&#xff0c;出现了这么问题&#xff1a;问题就是说这个文件已存在&#xff0c; 解决方法&#xff1a;覆盖之前的 ln -sf /usr/local/mysql/bin/mysql /usr/bin

读透《阿里巴巴数据中台实践》,其到底有什么高明之处?

最近阿里巴巴分享了《阿里巴巴数据中台实践》这个PPT&#xff08;自行搜索原始文章&#xff09;&#xff0c;对于数据中台的始作俑者&#xff0c;还是要怀着巨大的敬意去学习的&#xff0c;因此仔细的研读了&#xff0c;希望能发现一些不一样的东西。 读这些专业的PPT&#xf…

如果你也想做实时数仓…

数据仓库也是公司数据发展到一定规模后必然会提供的一种基础服务&#xff0c;数据仓库的建设也是“数据智能”中必不可少的一环。本文将从数据仓库的简介、经历了怎样的发展、如何建设、架构演变、应用案例以及实时数仓与离线数仓的对比六个方面全面分享关于数仓的详细内容。 …