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

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第1张图片

作者|姜沂(倾寒) 

出品|阿里巴巴新零售淘系技术部

导读:自 2014 年苹果发布会发布 Swift 之后,  Swift 经过多年迭代,终于达到了 ABI 稳定版本,也意味着 Swift 做为稳定的得语言,值得用在大型 APP, 用来生产环境中。

1、新晋网红SwiftUI——淘宝带你初体验         (点击阅读)

2、系列文章深度解读|SwiftUI 背后那些事儿   (点击阅读)

注: 项目代号为企业内部私有,这里使用 SOT 代指,意为 “Swift on Taobao”。

背景

为了研究 SwiftUI 在业务落地的可能性,我们一直持续关注着 SwiftUI 的发展,但编程这种工作,向来是阅读千编,不如实战一次来的深刻,刚好我们有一个业务场景非常适合,那就是观察稳定性大盘。

项目耗时

这里先给出时间结论,

整个 SOT APP 耗时 1.3 人力,共 10 个工作日,整个 Swift 代码 约 2800 行。

由于这是一款必须工作在内网下的 APP, 接入内网鉴权没有太多经验,花费不少时间。

项目设计

原型设计

做一款 APP 的最核心的部分是设计 APP 的功能,熟悉 SOT 的同学,应该知道一般观察稳定性主要是观察数据大盘,聚合列表,分析聚合详情,崩溃分析等比较重要的模块。

落地 SwiftUI 的计划预计 两周,所以 SOT 一期只做做核心常用的部分。功能有了,那么设计怎么办呢?

-)操作非常接近,花了一天时间简单设计了下界面。

这里刻意模仿 App Store的圆角和阴影设计,至于为什么?原因就是负责的设计会让 UI 代码编写变的更有挑战性,如果只是用系统原生的样式,那么碰见的难题就会大大减少,这样的实战到了实际的项目中,碰见的问题还会很多。

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第2张图片

数据流管理

SwiftUI 是一个典型的单向数据流得声明式 UI 编程框架, 在 SwiftUI 中 View 只是一个页面的描述部分,SwiftUI 提供了多个数据流管理对象。

@State @Binding  @Obserabled ,通过改变这些数据流的值,SwiftUI 系统可以理解重新构建 View Tree, 并根据内部变化的范围,有一层类似 Virtual Dom 的 ViewTree, 由于 View 都是结构体,SwiftUI 每次构建这个 View Tree 都极快,这使得性能有很强的保障。

State

State 是 SwiftUI 中最常用的 代理属性,通过对代理属性的修改,SwiftUI 内部会自动的重新计算 View的 Body部分,构建 出View Tree。

State 只能在当前 View 的 body 体里面修改,所以 State 的适用场景就是只影响当前 View 内部的变化的操作。

如 APP 选择界面中,图片资源都来源自网络。

示例代码如下 :


Binding

在传统的命令式编程中,GUI 程序中最复杂的部分莫过于状态管理,尤其是多数据同步,一个数据存在于不同的 UI 组成部分,UI 各个部分的变化理论上都有同步,状态量的变多加上异步的操作,会使程序的可读性直线下降,并且伴随着而来的就是 Bug ,并且不敢重构。

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第3张图片

SwiftUI 给我们的理念就是 Single source of truth, 简单来说就是单一数据源,单一数据源是个很早就有的名词/方法,但是很多系统并没有给出很好的解决办法,比如习惯 FRP 的同学可能用 RX/RAC 里面的 Singnal 去描述,但是 FRP 晦涩的概念,又使其在项目中的接入成本大大提高。

@Binding 。作者之前尝试自己实现一个 Binding,实现起来就是一个简单的闭包,通过闭包捕获 Source of truth 的数据,同时 SwiftUI 会帮我们自动刷新需要同步的界面。使我们的数据同步变的的非常简单。

实际例子如,系统提供的 Control(可操作的View) 的构造器基本都需要 @Binding  属性,可以自动的同步来自 API 调用方的数据源。

这里举个例子如 项目中的版本选择和日期选择功能,我们需要讲控件选择的值同步给数据源。

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第4张图片

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第5张图片


ObservableObject

ObservableObject 在 Xcode11 Beta 4 之前叫 ObjectBinding , 这个类型是一个协议,要求我们实现一个来自 Combine 框架的 Subject Subject 是一个和命令式编程世界交互的桥梁,是一个特殊的 Publisher,SwiftUI 内部会自动的订阅这个 Subject,在  Subject 发送变化时 SwiftUI 会自动刷新数据。

@Published 是 Xcode11 beta5 之后新增的代理属性,此属性如果用在 ObservableObject 内,如果属性发送了变化,会自动触发 ObservableObject 的 objectWillChanged 的Subject变化,自动刷新页面。

同时由于 Combine 框架的支持,多个条件联动变成了一个简单的事情,在 SOT APP 项目中,就非常适合,比如数据大盘,有将近10几个数据状态,任何一个触发,都会导致数据刷新。

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第6张图片

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 提供了一套非常简单清晰的标准,可以用在多个平台上交互,并提供一致的表现力。

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第7张图片

需要注意的是 UIViewRepresentable 的遵守者,是一个 View 容器,此容器会被创建多次,如果内部有数据源需要通知,需要创建相应的 Coordinator 将当前的容器当做 View 传递进去,由于 View 是结构体。

Coordinator 修改的部分,最好只是 ObservableObject Binding

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第8张图片

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 ,在某个时刻处于某种状态。

  1. 状态在页面表达中是一个简单的树型结构,在 SwiftUI,对应的 就是 View Tree。

  1. View 操作不能直接修改状态,只能通过发送 Action, 间接改变 Store。

  1. Reducer 通过 Action 加上 oldState 获取 newSatete。简单来说就是  State  = f(action+oldState)。

附上一份 阮一峰的Redux入门教程的示例图:

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第9张图片

这套风格在前端大型项目中已经了验证,可以比较清晰的表达用户事件交互和状态管理。ObserableObjectEmviromentObject

SOT 项目一期暂未采用,在二期项目中会探索合适的架构设计。

项目总结

此项目在短短的 10 个工作日内就能完成,不得不说 SwiftUI 的开发效率真的惊人,虽然目前还有一些 Bug ,但是相信在未来,SwiftUI 会是 Apple 平台 UI 布局的解决办法,关于 SwiftUI 如何在淘系落地业务,还在持续探索中。

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

One More Thing

淘宝基础平台团队正在举行2019实习生(2020年毕业)和社招招聘,岗位有iOS Android客户端开发工程师、Java研发工程师、C/C++研发工程师、前端开发工程师、算法工程师。

欢迎投递简历至(君展): [email protected]

如果你想更详细了解淘宝基础平台团队,欢迎观看团队介绍视频更多淘宝基础平台团队的技术分享,可关注淘宝技术微信公众号AlibabaMTT

参考

关注「淘宝技术」微信公众号,回复「链接」,即可获得全部参考网址~

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第10张图片

1、Session 402 What's New in Swift  

2、Session 204 Introducing SwiftUI: Building Your First App

3、Session 216 Introducing SwiftUI Essentials

4、Session 226 SwiftUI Essentials

5、Session 231 Integrating SwiftUI

6、Session 237 Building Custom Views with SwiftUI

7、Session 238 accessibility in swiftui

8、Session 240 SwiftUI on all Device

9、Session 219 SwiftUI on watchOS

10、Session 415 Modern Swift API Design

11、FSCalendar

12、Kingfisher

13、Redux 入门教程(一):基本用法

14、Redux 中文文档

END

你可能还喜欢

点击下方图片即可阅读

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第11张图片

系列文章深度解读|SwiftUI 背后那些事儿

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第12张图片

新晋网红SwiftUI——淘宝带你初体验

历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?_第13张图片

640?wx_fmt=png

你可能感兴趣的:(历时五天用 SwiftUI 做了一款 APP,阿里工程师如何做的?)