本文中所有 Demo 运行的环境依赖
前言
近年来 Android 和 iOS 开发在两家上游商业公司的推动下,不断尝试着一轮一轮的变革:
其中很多理念和前端社区的走向也是相同的,比如:
本文会简要分享下 SwiftUI 的上面两点理念,以及介绍下 Xcode 最新的一些工具设计。
第一印象
如果你还不知道 SwiftUI 是什么,那也没关系,反正我也不打算从 SwiftUI 开始讲起...
为了让广大前端工程师同学打起兴趣,首先我打算先介绍下 SwiftWebUI,它是 Github 上一个开源的将 SwiftUI 跑在 Web 上 的实验性项目。
看看官方的一个简单计数器 🌰:
Swift 的包管理是基于源文件 + 配置文件的,也遵循 semver 规则。
效果:
从上面这个小例子中就可以看到到 SwiftUI 的几个重要理念了:
更复杂的 Demo 可以体验:https://github.com/swiftwebui/AvocadoToast
声明式 UI
Demo
Demo 参考:https://developer.apple.com/tutorials/swiftui/creating-and-combining-views
SwiftUI 的文档这次设计了非常精美的 tutorials 交互,可以跟着一步一步的操作。
-
所有视图组件类型都是
View
,它只需要实现一个接口
body
,可以类比为 React Component 的
render
-
VStack
是垂直布局的容器,
HStack
是水平布局的容器,
ZStack
是独立图层的容器,类似前端 zindex
-
组件可以有属性(Text 的参数),组件也可以应用 ViewModifier(链式调用)
对 SwiftUI 视图语法的深入分析可以阅读:SwiftUI 的一些初步探索 (一)
DSL
WWDC-2019 Session 216: SwiftUI Essentials
SwiftUI 和 Flutter 的 UI 语法都可以看见 React JSX 的影子,它们不约而同使用各自的声明式 DSL 来描述 UI 。但是这和前端常用的模板语言(Nunjucks/Vue)还是有一些区别的,它们都是复用语言本身的逻辑控制语句(条件,循环),再加上一种特殊的数据结构(eg: React Virtual DOM)来表达 UI。毕竟对于 Google 和 Apple 来说,语言、编译器、开发工具、开发者、平台这整条链路都在掌控中,那么可以做一些集成度更高的设计。
我认为使用类似 Nunjucks 此类模版语言的一些缺点:
-
和业务逻辑的编程语言之间有一层 gap,发生关联的地方会丢失编辑器智能提示,跳转等上下文信息 (通过开发编辑器插件等也可以实现,但成本较高)
-
模板内部的属性绑定,循环,变量插值一些场景会出现字符串形态的逻辑代码
-
模板语言仍需要一次提前编译,比如 vue 模板的编译:https://vuejs-tips.github.io/compiler/#v-html
仔细看一下 SwiftUI 的一些视图语法:
条件:直接使用条件控制语句
循环:直接使用循环控制语句
List:
如果是动态 List 的场景下,SwiftUI 还会要求开发者提供 Identifiable:类似 react 的 key 或者 antd Table.props.rowKey, 其作用是让框架可以精确定位和更新 List 中的某一行元素
使用子元素来表达结构:
Form:
此外按 从 SwiftUI 谈声明式 UI 与类型系统 这篇文章的分析,因为 SwiftUI 强类型的,那么一个组件可能的几种视图结构是可以被静态分析出来的,那么在运行时甚至可以基于静态分析的类型信息来对视图的更新算法做优化,可以达到比 React Vitual DOM 更好更新性能。
ViewModifier
官方的文档这么介绍 ViewModifier :A modifier that you apply to a view or another view modifier, producing a different version of the original value.
它的用法有点类似 React 的 HOC(High Order Component),附加在某个视图上改变它原本的结构或样式。除了内置的 modifier,SwiftUI 也允许开发者定制自己的 modifier 的,比如下面是一个自定义例子:
而且 modifier 是应用于整颗子树的,例如下面这两种写法都可以实现禁用整个表单的效果:
Stack & 布局
Stack 前文已经介绍过,是内置的一些布局容器
虽然没有真实开发过 ios 应用,但是从 App Store 里面的 App 设计风格来看,iOS 由于屏幕尺寸小,单页内部的元素层级不多,布局也不会太复杂,并且时常搭配 NavigationView,TabView 这种导航类的组件继续对视图做细粒度切割。而且 Apple 对设计规范的掌控力非常强,因此 SwiftUI 的布局体系相对比 Web 要简单很多,有点类似简化版的 Flex。
系列文章深度解读|SwiftUI 背后那些事儿 这篇文章对 SwifUI 布局算法有一部分解释
跨平台?
内置组件是有跨平台的不同实现的,有点类似当初 React Native 的做法:
但是 Apple 官方是说为了用户体验考虑,不推荐为 Mac,iPhone,Apple Watch, Apple TV 这些不同尺寸的设备采用一套界面设计,因此他们不推崇
Write
once
,
run anywhere
,而是宣称
Learn
once
,
apply anywhere
。
响应式数据
响应式数据在前端领域已经有非常多的实现了,这里只大致介绍一下:
What?
假设抽象的认为组件就是消费一些数据去生成特定区块视图的逻辑封装体,那么
Component
都是满足
State
=>
View
这么一个协议的封装。而响应式数据的最重要效果就是 当依赖的数据(State)发生变化的时候,组件视图(View)会自动更新。
Why?
那么为什么要采用这种开发模式呢?下面这张图来自 WWDC 2019 - Session 204:Introducing SwiftUI: Building Your First App , 诠释得非常好。
GUI 程序通常使用场景是处理一个长时间段的用户交互过程,这其中涉及到许多用户输入信息,视图临时状态的存储。此外整个页面树上可能有数以百计的元素之间要互相通信和联动。如果按照传统的事件驱动的编程模型,依靠程序员的大脑去掌控所有数据到视图,数据到数据之间的衍生依赖关系,脑力负担是非常大的,一旦这个复杂度超越了人脑的算力,就会导致关联关系不正确,也就是 Bug 的产生。
而“响应式”就是将这个依赖关系的维护交由框架来处理,开发者只需要“声明”出依赖关系,而且这个“声明”是指用对业务逻辑的描述语言来顺带地描述出这种依赖关系,比如:
在这个理论体系下,State 始终是 single source of truth,视图只是数据的一个衍生值(Derived Value)。
SwiftUI 用响应式状态管理和数据绑定避免了大量的命令式控制逻辑,从而使应用开发者用 “数据驱动” 或 “模型驱动” 的替代传统的事件驱动,开发者的脑海中始终在思考此刻控制我的视图的数据状态是如何,而背后衍生的视图如何更新,则不用关注。
当然这种模式也有一些弊端,参阅:vue.js 会是那颗银弹吗?
How?
以前端领域的 Observable 实现库 @nx-js/observer-util 的 Demo 为例子:
可以看出这里分几部分:
-
定义响应式数据:想方设法利用语言特性实现对对象的读写(getter,setter) 做拦截
-
vue@2 是使用 JS defineProperty 特性
-
observer-util 和 Vue@3 都是利用 JS Proxy 特性
-
SwiftUI 是利用 Swift 语言的 Property Wrapper 特性,@state @Binding 在 swift 语言标准里都属于Property Wrapper 的实现
完成上面两步之后,一旦框架监听到某个对象的 setter,就从依赖的 key-value Map 里面找到当前对象的依赖函数列表,一一执行。
更详细的解读可观看:WWDC 2019 - Session 226: Data Flow Through SwiftUI
SwiftUI 的状态管理 API:
-
@State:定义一个响应式状态,它的变化会导致依赖它的视图自动更新(单向)
-
@Binding:视图和数据的双向绑定
-
@ObjectBinding:作用等价于 @Binding,但是支持使用一个外部对象
-
@BindableObject、Combine:Apple 官方新发布的 combine, 说是用来处理外部事件或服务端推送等场景, 其实笔者也不懂这是个啥,但只要说它是 RxJS 的 Swift 版本实现,大家应该就会心一笑吧 🚗
-
@EnviromemntObject:沿着 View 树的层级一直向下共享的数据,实现类似 Scoped Data 的效果
-
@Enviroment:可以全局共享的数据,同时它的变化会导致 UI 自动刷新,类似 React 这边的 Provider
Xcode 工具设计
首先 Xcode 一直是一个非常强的 IDE,包括项目管理,依赖管理,版本管理,开发测试构建等等项目研发需要的功能它都支持。
本文主要介绍新红框圈出来与 SwiftUI 有关的一些新功能。
Live Preview
对于 UI 开发来说,能实时预览而不用泡杯咖啡等待构建真是太愉悦了,Flutter 也是将 Hot Reload当作一个很重要的卖点来打,而 SwiftUI 更是将这个体验做到了极致。
上图右下角红框的图标,点击即是在 Live Preview 和传统的 Build Simulator Preview 之间切换,Apple 把这两种预览都做到了一个位置,just click to switch!
为什么 Live Preview 能做到极其快?