如何评价 SwiftUI?

Xcode - SwiftUI- Apple Developer
关注者
1,242
被浏览
869,578

63 个回答

原文: zhuanlan.zhihu.com/p/78

SwiftUI:Better apps. Less code.

本文中所有 Demo 运行的环境依赖

  • macOS Catalina 10.15 Beta
  • Xcode 11 Beta5

前言

近年来 Android 和 iOS 开发在两家上游商业公司的推动下,不断尝试着一轮一轮的变革:

  • Google 2011 年发布 Dart,2017 年发布 Flutter,同年 Google I/O 2017 上宣布 Android 官方支持 Kotlin, Google I/O 2019 上宣布 Kotlin-first,同时发布 Kotlin Jetpack Compose。
  • Apple 在 2014 WWDC 上发布 Swift,在 2019 WWDC 上发布 SwiftUI。

其中很多理念和前端社区的走向也是相同的,比如:

  • 声明式 UI
  • 响应式数据

本文会简要分享下 SwiftUI 的上面两点理念,以及介绍下 Xcode 最新的一些工具设计。


第一印象

如果你还不知道 SwiftUI 是什么,那也没关系,反正我也不打算从 SwiftUI 开始讲起...


为了让广大前端工程师同学打起兴趣,首先我打算先介绍下 SwiftWebUI,它是 Github 上一个开源的将 SwiftUI 跑在 Web 上 的实验性项目。

看看官方的一个简单计数器 :

  • 创建一个空的 macos command line 应用
  • 使用 Swift Package Manager 从 github 上拉取 SwiftUIWeb 依赖包
Swift 的包管理是基于源文件 + 配置文件的,也遵循 semver 规则。
  • main.swift 粘贴如下代码,最后添加一行启动服务 SwiftWebUI.serve(MainPage())
  • 点击 Xcode build,在浏览器打开 http://localhost:1337



效果:


从上面这个小例子中就可以看到到 SwiftUI 的几个重要理念了:

  • 声明式 UI (类比 React JSX,Flutter)
  • 响应式数据 (类比 mobx,vue)
  • “CSS In JS”
  • 链式调用(官方术语叫 ViewModifier,类似 react 里面的 HOC 概念)
更复杂的 Demo 可以体验:github.com/swiftwebui/A


声明式 UI


Demo

Demo 参考:developer.apple.com/tut

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 模板的编译:vuejs-tips.github.io/co



仔细看一下 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 前文已经介绍过,是内置的一些布局容器

  • HStack:水平排布它的子元素的一个容器视图。
  • VStack:垂直排布它的子元素的一个容器视图。
  • ZStack:层叠排布它的子元素的一个容器视图。
  • Spacer:一个弹性的空白间距元素,会充满容器元素的主轴方向
  • Divider:一个虚拟的分界元素 完整列表可以参考:View Layout and Presentation




虽然没有真实开发过 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 => View 状态到视图的过程中,生产视图的过程中消费(getter)了哪些数据,就可以大体认为是该视图对该数据建立了依赖



  • State => Drived State 衍生状态,比如计算属性的定义过程中,该属性的终值依赖了哪些中间数据,就可以认为是该属性对其他属性建立了依赖

在这个理论体系下,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 的实现



  • 建立依赖关系:observer-util 是通过在 function 执行的前后打上标志位,记录下整个方法执行过程中的依赖关系 key-value map,其 key 是发生 getter 的 JS 数据对象,value 是 function 本身

完成上面两步之后,一旦框架监听到某个对象的 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:基于 AST 分析,无构建流程,实时刷新,无交互(不能点击和跳转)
  • Simulator Preview:传统构建流程,真实应用运行(和传统预览一样,但不用切换屏幕到模拟器了)

为什么 Live Preview 能做到极其快?

  • 对当前文件进行 AST 分析, 找到所有遵守 PreviewProvider 协议的类型进行预览渲染,只展示静态的视图结构
  • 监听代码或画布的变化,只差量更新最细粒度变化的元素部分

如何处理 Mock 数据,预览态与真实运行态的差异?
Preview is Code.
下图左下角的代码可以看出 #if DEBUG #endif 说明是编辑时代码,每一个 SwiftUI 视图文件都可以在这个编辑时代码区域内实现一个 PreviewProvider 方法,在这个内部,开发者可以灌入 mock 数据渲染开发好的视图组件,甚至可以准备多套mock 数据或者环境参数,同时在右边展示出视图的多种状态。




添加资产

点击编辑器右上角,会弹出浮层,因为浮层不占用编辑器控件,所以这里甚至可以容纳组件文档:


这个浮层的信息架构还有二级分类,第一个 Tab 是 View (组件资产),其它几个 Tab 分别是:
Modifiers:


Snippets:



Media:



Color:


同时在资产面板这边可以看出 Apple 的一些设计细节:
资产面板可以从 List 模式切换到 Grid 模式:


资产面板可以折叠文档区域:



Inspect(属性面板)



在画布或代码视图中选中某一个元素,command + click 即可呼出 action 面板,第一位 action 就是 Inspect,先介绍下 Inspect 面板的内容:

基础属性:


布局 padding:


宽高:


除此之外 Action 列表里面还有一些提效的操作(实质是对视图代码的智能转换),比如对

  • 视图层级结构的快速改变:在元素外面快速套上一些常见的布局容器
  • Repeat 迭代:将单个元素转换成 for 循环包裹
  • Conditional 条件:将单个元素转换成 if else 包裹
  • Optional 可选:将单个元素转换成 if 包裹


代码和视图双向同步

SwiftUI 实现了一个闭环,实际体验还是不错的。

  • 选中代码可定位视图,选中视图也可以定位代码
  • 选中视图点击可唤出属性面板,修改属性可自动回写到代码
  • 资产面板拖入可以拖入到代码,也可以拖入到视图


Code Lint & AutoFix

代码编写过程中的实时错误提示与 AutoFix



Extract SubView

  • Action 面板里有一个一键将某一块视图提取成子组件的操作
  • 提取完成之后,如果该子组件内部使用到了外部变量,Lint 系统会检测到并报错

结语

因为笔者也在做 Web 端中后台 Low Code 可视化建站的产品,虽然 SwiftUI 是 Pro Code 开发模式的工具,但是正如 SwiftUI 的宣传语鼓吹的一样:

  • "The shortest path to building great apps on every device" ,
  • "Better apps. Less code."

大家努力的方向都是一致的,如何用更少更简洁的代码,更直观更高效的工具来提速研发流程,真正解放生产力。
同时 SwiftUI 的方向我认为是正确的,它结合了几乎当前整个领域所有最优秀的理念和设计:

  • 强类型的语言让代码维护性更高
  • 声明式 UI 让视图代码更简洁
  • 响应式状态管理让开发者心智更简单
  • 可视化和代码的两种编辑模式双向转换,让新手用户可以通过可视化编辑器边开发边观察生成的代码学习,熟练用户可以完全代码编辑,使用画布进行实时的辅助预览
  • 从设计之初就保证对应用代码的理解力,那么类似一键提取自定义组件,一键转换成 repeat,list,condition 这些智能操作会越来越多

朝着正确的方向,辅以 Apple 强大的工具整合和体验打磨能力,加上它完整的链路掌控,全平台覆盖的设计规范,只要持续打磨,SwiftUI 应该会在 iOS 开发领域得到普及。


相关资料

0709重磅更新:今年苹果WWDC 20 刚结束,SwiftUI 相关新特性和介绍视频的释出,都明确的宣告着 SwiftUI 元年已经到了,SwiftUI 已经成长为新时代的布局引擎。

今天主要围绕WWDC 20最新的资讯,来讲讲 SwiftUI 到底对我们意味着什么?

本篇回答内容来自于阿里巴巴淘系技术部高级开发工程师倾寒。

——————————————————————————————————————

前言

SwiftUI 是苹果公司于 2019 年推出的 Apple Platform 的新一代声明式布局引擎,笔者于去年第一时间升级 Beta 尝鲜全家庭,并在短时间内迅速落地了基于 SwiftUI 的内部 APP, 也分享了几篇关于 SwiftUI 的文章, 但 SwiftUI 1.0 基本没有任何公司敢用在正式上线的主 APP 上,API 在 Beta 版本之间各种废弃,UI 样式经常不兼容,大列表性能差,彼时都标识着 SwiftUI 还称为一个 Toy Framewrok.

随着 WWDC 20 相关新特性和介绍视频的释出,都明确的宣告着 SwiftUI 元年已经到了,SwiftUI 已经成长为新时代的布局引擎。


以下从这几个方面分享关于 SwiftUI 的重大改变及核心优势。

PS: 需要读者对 Swift 及 SwiftUI 1.0 有一定熟悉。


SwiftUI Apps

苹果在最近几年的动作中一直在搞 Apple Platform 统一的事情,从最近几年的 iPad 多任务 多窗口,到 Mac Catalyst 再到今年更进一步直接推出了 Apple silicon 芯片更是从硬件上做到了真正统一(话外音: 你们在软件上玩的那些跨平台的都是小玩意,硬件才是王道)。
还提供了 Rosetta2 Universal2 帮助开发者基本无成本的迁移到新平台上。但是作为软件工程师还是要更多的关注软件生态的变化。首先了解下创建 APP 时的变化


可以看到创建新工程时有了一套全新的模板基于 SwiftUI App Lifecycle 的跨平台项目。
代码也从原本的基于 UI/NS HostViewController 变成了基于 APP 的声明式描述,下面是代码的前后对比

  • Before
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let contentView = ContentView()
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
  • After
import SwiftUI 
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

其中@main 是Swift 5.1 新增的 Attribute 标记了应用程序的入口点,更多请参看SE-0281-main-attribute.md

乍看好像只有代码精简了不少,很多人会认为这个简洁程度还不如Flutter 的 main() => runApp(MyApp());.

但最重要的变化是这是第一次跨平台代码,完全无需引入任何 UIKit APPKit WatckKit 等相关Framewok, 即可直接运行在不同平台上。这意味着我们后续在UI布局系统上可以逐渐摆脱对传统命令式 UI 编程的依赖。达到真正的平台无关。

SwiftUI 将整个原有的平台差异部分抽象为 App 和 Scene,对于一个 mac/iOS/iPad/watch/tv/..应用,来说 App 代表了整个应用,Scene 代表了与 Window 相关的多窗口,有些设备只有一个 Scene 有些则有多个,虽然不同的 OS 确实存在差异,但是在语义层面达到了一致。

其次一个没有历史包袱的 APP,也可以完整的从 Swift APP lifecycle 风格式的模板开始,无需再和传统的 UIKit/APPKit 等混合。这也意味着可以达到 APP 完全 Declared and State-Driven




Viusal Editing

Preview

在传统的利用 DSL 可视编程框架或者平台,诸如 Web Flutter 等技术,都是开发者编写好对应的代码,运行在对应的平台或者调试工具上。 SwiftUI 作为苹果最重要的软件层战略框架,更是和 Xcode 深度结合,在运行之前就可以完整的预览你所编写的界面。

强大的 Preview 可以让你既可以从编写 DSL 到立即预览效果,也可从预览的 Canvas 画布中直接修改效果在代码编辑器中生成代码,这对于日常开发的效率有非常大的提高,尤其是在 UI 微调时,效果尤为突出。
Xcode12 可以在 Canvas 上同时预览多个不同设备环境的界面,也可以直接投射到真实的设备上来预览。


对于日常开发来说,编写一个UI界面通常依赖外部的网络/磁盘/其他数据,才能正常的构建,这也造成了UI开发虽然是开发中较为简单的一步,但同时也是最耗时的一步,有了预览功能,可以把很多繁琐的工作前置解决掉,对于研发效率会有非常大的提高。

Xcode Library

在编写真实项目中,一个公司的 APP UI 包含成百上千种风格的 View 组件,对于 UI 组件丰富的产品,如果一个新需求可以由现有的组件组合,那么需求交付的时间也会大大缩短。
但是对于一个大型的开发团队而言,一个开发同学是很难知道公司内到底有多少种组件库,而且即便知道有某种组件库,开发同学初期看到的也是代码,一般需要书写一定的 Demo 才可以用眼睛感知到这个组件到底是否是我想要的。
在 Xcode 12 中提供了更强大的工具,一个自定义组件,只需要遵守一个 LiberyContentProvider 协议就可被Xcode识别,可以像系统控件一样直接从 Xcode 里面识别并预览。对于一个大型团队来说,此功能可以大大提高找寻组件和查看组件样式的效率。


struct MyXcodeViewLibrary: LibraryContentProvider {
    var views: [LibraryItem] {
        [LibraryItem(TaobaoGoodCard(), visible: true,
                    title: "taobao商品卡片", category: .control, matchingSignature: "taobao shop card view"),
         LibraryItem(TaobaoLiveCard(), visible: true,
                     title: "taobao直播卡片", category: .control, matchingSignature: "taobao live card view"),
        ]
    }
}

DSL

随着 Swift5.3 和 SwiftUI2.0 的推出,SwiftUI 在 DSL 上也更富有表现力, Swift 支持了多重尾闭包语法和在 ViewBuilde 里面支持 Switch Case 语句。

Multiple Trailing Closures

虽然社区对多重尾闭包的讨论上一直存在争议问题,但最终 Swift5.3 还是接受并实现了,在普通命令式编程的地方使用会有一定的困惑性,但是在 SwiftUI 中
DSL 也更有声明式的味道。

// Without trailing closure:
        UIView.animate(withDuration: 0.3, animations: {
          self.view.alpha = 0
        }, completion: { _ in
          self.view.removeFromSuperview()
        })
        // With trailing closure
        UIView.animate(withDuration: 0.3, animations: {
          self.view.alpha = 0
        }) { _ in
          self.view.removeFromSuperview()
        }
        // Multiple trailing closure arguments
        UIView.animate(withDuration: 0.3) {
          self.view.alpha = 0
        } completion: { _ in
          self.view.removeFromSuperview()
        }

Switch Case Support

在 SwiftUI 的 ViewBuilder DSL体系中也支持了 Switch case 语法。

var body: some View {
            switch c {
            case .a:
                return Text("A")
            case .b:
                return Text("B")
            case .c:
                return Text("C")
            }
        }

Data Flow

在使用传统命令式编程编写 UI 代码时,开发者需要手动处理 UIView 和 数据之间的依赖关系,每当一个 UIView 使用了外部的数据源,就表明了 UIView 对外部的数据产生了依赖,当一个数据产生变化时,如果意外的没有同步UIView的状态,那么 Bug 就产生了。

处理简单的依赖关系是可控的,但是在真实项目中,视图之间的依赖关系是非常复杂的,假设一个视图只有 4 种状态,组合起来就有 16 种,再加上时序的不同,情况就更加复杂。
人脑处理状态的复杂度是有限的,状态的复杂度一旦超过人脑的复杂度,就会产生大量的 Bug,并且修掉了这个产生了新的Bug。


那么 SwiftUI 是如何解决这个问题的?


SwiftUI 的框架提供了几个核心概念:
1. 统一的 body 属性,SwiftUI 自动从当前 App 状态集自动生成基于当前状态的快照 View。
2. 统一的数据流动原语。

关于 SwiftUI 中的 Data Flow 是如何消除视图和状态不一致的,请参考去年撰写的文档 系列文章深度解读SwiftUI 背后那些事儿


今年 SwiftUI 2.0 新增的 StateObject 数据流原语让 SwiftUI 在重复创建 View 时避免重复创建 ObservedObject 从而提高 View 重建的性能。


SceneStorage 和 APPStorgae 让一些可持久化的数据变得更加简单且具有语义化。

New Controls

前面提到的,新增的 DSL 语法 SwiftUI App Lifecycle,以及 Xcode Library Preview 其实本质上都是对去年 SwiftUI 1.0 锦上添花的新扩展
真正重要的是今年新增的各类新控件,其中通过导出来自 Xcode11.5 和 Xcode12.0 beta 版本的 Swift 声明文件,可以观察到整个声明文件从原来的 10769 行增加到 20564行。
新增了约 87 个 struct 16 个 protocol。 有了这些丰富的组件才可以更好的构建我们的 APP 。

大列表组件

在任何一款 APP 中都会存在类似大列表组件,如淘宝 APP 里面的某家店铺里面商品列表流,首页的信息流,都是具有超长内容的列表页数据。 对于长列表页来说,过长的 UI 页面会导致过多的内存占用,在用户的设备中,内存是最为重要的指标,对于目前国内的 APP 市场,低端手机仍然占据大量的市场,对于这些设备来说,一旦内存超标,APP 就很容易 OOM,这会导致用户体验非常差,在现有竞争关系激烈的市场环境下,体验差意味着会失去用户。

对于传统的命令式编程来说,我们可以主动控制 UITableViewCell 的重用,自建缓冲池等一系列手段去优化我们的 APP 内存占用,但是对于 SwiftUI 1.0 来说,系统提供的控件并没有有效的办法去让我们控制页面的渲染,对于大列表页面就容易出现内存占用过高的问题。
SwiftUI 2.0 推出了 LazyHStack 和 lazyVStack 加上 List 渲染模式默认就是 Lazy 的直接解决了最大的性能问题,

笔者以去年使用 SwiftUI 编写的 Emas App 为例,当列表页(并无大图)加载到 500个时, APP 使用内存已经达到了将近 360MB 。而只需要切换到 Xcode12 API 调整为到 LazyVStack 内存占用直接降低 300MB



Widget and Clips

苹果与 WWDC 20 推出的 WidgetKit 支持的 API 是 SwiftUI Only,虽然已经可以混合部分UIkit 里面的View,但相信没有历史包袱 最低支持版本为 iOS14 的 Widget 没有人会选择笨重的命令式 API。
同理 Clips 也一样。 这里因为篇幅原因就不做展开,后续会有专门的文章分析相关技术。

Swift & SwiftUI 的机会在哪里?


笔者曾经在公司推动集团升级了基建,支持了 Swift 开发环境也在淘宝落地了一些场景,但是集团内一直有一些质疑的声音, 引入 Swift 到底有什么用?


SwiftUI 又是 N 年后才可以用上的小玩意,Objective-C 不够用吗?现在笔者可以回答这些质疑的声音, Swift 未来的机会在 效率体验


效率

从研发效率上来说, Swift 对比 Objective-C 的精简程度不言而喻,笔者在淘宝 APP 上线的模块代码量下降了 40 %
但更进一步,如果编写 UI 界面从 UIKit 转向了 SwiftUI 代码量直接少了不止一倍。 更少的代码意味着更快的交付,在目前竞争激烈的市场会有更多的试错场景。

关于使用 UIKit 编写代码转向 SwiftUI 的代码量对比,读者可以参考开源 APP MovieSwiftUI 直观了解


体验

读者可能比较困惑对于切换语言和框架,对体验看上去没有任何帮助,但事实真是这样吗?
首先引入 Swift 后,由于 Swift 语言设计之初便对安全性列为最重要的目标,Swift的引入会让代码尽可能的减少未定义的行为,减少 Crash 意味着APP的稳定性提高,体验自然更佳。

其次虽然 Swift 同样的语言出于对安全性考虑编译处理的指令会比 Objective-C 更多,但是如果UI部分都用 SwiftUI 来写呢?
更少的代码意味者更小的包大小,目前国内巨头 APP iOS 端 APP 包大小都朝着 200 MB 奔去,如果能减少更多的代码对包大小也可以在 200MB 的限制下承载更多而业务。对用户的体验也有较大的提升。

更进一步由于 Swift 选择使用值类型构建整个APP,值类型的有点在于更扁平化的内联数据结构去分配内存,而不是使用更多间接指针引用,减少了大量不必要的堆内存消耗,意味着整体内存使用量的降低,对整个 APP 的稳定性也有较大的提高。



苹果的选择


Swift 做为苹果的战略语言已经发展的越来越壮大,自 2019 年 Swift ABI 稳定后,苹果在 Swift 的投入越来越大。我们可以进入 /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/swift , /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks , https://github.com/applehttps://github.com/swift-server 看到, 自 iOS 13 以来 苹果新增了约 10+ Pure Swift Library , 10+ Open Source Swift Library, 以及针对 144 个公开 Framework,根据 Swift Style 重新设计了 57 个 Framework 的API。
从以下数据


  1. 从 WWDC17 后 苹果已经不再使用 Objective-C 做 Sample Code 演示
  2. https://developer.apple.com/不再更新 Objective-C 相关的文档
  3. WidgetKit 是 SwiftUI only。
  4. App Clips 10M的包大小, SwiftUI 是最合适的框架
  5. 开源社区逐步放弃 Objecive-C 如 Lottie。


可以判断,Swift 是未来 Apple 平台的唯一选择,越是有包袱的大厂 APP,从现在还不尽早储备,在未来越会寸步难行。


我们需要做些什么?

Swift

我们已经做了什么

  1. 一套支持 Swift 二进制的研发环境
  2. 300+ 支持了混编的淘系 SDK。
  3. 手淘落地了 6 个模块。
  4. 集团新增了约 20个 支持 Swift 的APP。
  5. 10 多场技术培训。
  6. 169+ 语雀知识沉淀。
  7. 300+ 工程师的集团 Swift 官方组织。
  8. 2 个 技术创新产品

经过去年横向组织大家共同的努力,我们已经已经支持了横向大基建。包括研发环境,工具支撑,沉淀了大量的文档,还有相关的技术课程。


要朝什么方向去努力

目前集团对于 Swift 的呼声越来越高,我们大量的工程师希望的去使用 Swift 。目前首先要做的事情是依托 Swift 和 SPM 提升我们的开发体验,升级我们的中间件,使业务可以大量的用起来 Swift ,提高我们的研发效率和代码质量。

  1. 升级基于 SPM 的新的包管理体系
  2. 升级老旧基础库,打磨新一代基建。
  3. 引入新的 Swift 特有库 赋能业务。

SwiftUI

虽然前文提到了 SwiftUI 的众多优点,包括研发效率,体验的提高,但是在国内的环境中 SwiftUI 也有它致命的弊端

  1. iOS 14 才可放心的使用。
  2. 只支持 Apple Platform,这和国内的要支持 Mobile Platform 从理念上冲突。

大型 APP 要解决的是如何部署到低版本操作系统上和安卓平台上,毕竟很多公司还在支持 iOS 9 对于升级到最低支持 iOS 14 好像还需要一个世纪那么漫长,而且国内的设备占比大头还是以 Android 巨多 。


虽然可以看到 Swift 语言也在逐渐支持 Android 平台,但是也看到苹果对于安卓平台的 SwiftUI 并没有太大兴趣。

从体验上 Flutter 远不如 SwiftUI 这种亲儿子效果好, 但对于国内跨端欲望旺盛的市场来说 SwiftUI 还是比不过 Flutter, 不过既然 SwiftUI DSL 层已经基本固定,那么也有可能投入人力直接在低版本操作系统上实现一套自建的 SwiftUI 引擎,或者将 SwiftUI 引擎移植到安卓平台,比如对接 Flutter 或者直接对接 Android Native。


比起 Flutter 引入双端带来的包大小增量和体验不一致的情况, SwiftUI 保留 iOS 平台体验,只侵入一端的选择显然要更好一点。

不过短期内我们可以在 Clips 和 Widget 场景下开始使用 SwiftUI, 毕竟 SwiftUI 快速的开发效率对和较低的包大小占用非常适合这样的场景,我们可以在业务场景中练兵储备我们的 SwiftUI,并积极在主 APP 中尝试。


参考

  1. SwiftUI 背后那些事儿
  2. MovieSwiftUI
  3. SE-0281-main-attribute.md
  4. Add custom views and modifiers to the Xcode Library
  5. Structure your app for SwiftUI previews
  6. Introduction to SwiftUI
  7. What's new in SwiftUI
  8. App essentials in SwiftUI
  9. Visually edit SwiftUI views
  10. Stacks, Grids, and Outlines in SwiftUI
  11. Build document-based apps in SwiftUI
  12. Data Essentials in SwiftUI
  13. Build a SwiftUI view in Swift Playground
  14. Build SwiftUI apps for tvOS
  15. Build SwiftUI views for widgets
  16. Build complications in SwiftUI
  17. What's new in Swift
  18. Swift packages: Resources and localization
  19. Distribute binary frameworks as Swift packages
  20. Explore logging in Swift
  21. Create Swift Playgrounds content for iPad and Mac
  22. Embrace Swift type inference
  23. Explore numerical computing in Swift
  24. Unsafe Swift
  25. Safely manage pointers in Swift
  26. Explore numerical computing in Swift [
  27. Explore Packages and Projects with Xcode Playgrounds
  28. Use Swift on AWS Lambda with Xcode

本文作者:倾寒 来自淘宝客户端iOS架构组


欢迎投递简历~ 手淘客户端架构组,负责淘宝客户端的基础框架包括组件化容器、启动器、路由、UI框架等,负责高可用包括Crash、卡顿、内存、耗电等监控,负责全局性能、体验优化,负责重点技术包括存储、日志、修复等,负责系统新特性、新技术、新设备探索,在这里你会面临海量用户、大规模业务、双十一大促带来的巨大技术挑战,你能与资深大牛并肩作战,深入系统内核研究解决复杂问题,迅速成长为业界优秀工程师!
简历投递:qinghan.jy@alibaba-inc.com


—————————————————————————————————————————

以下为旧回答。

SwiftUI 做的很多事情和 Flutter 很相似。目前 Swift UI 支持 Apple 全平台. 但是要知道的是 安卓是开源的 iOS 是闭源生态,Flutter 把手伸到 iOS 平台不太容易,但 SwiftUI 把手伸到安卓平台可就容易多了。

今天,我们主要从 SwiftUI 与 Flutter 对比的角度,来聊一聊。

本篇回答内容来自于阿里巴巴淘系技术部高级开发工程师倾寒。

————————————————————————————————————————

苹果在 WWDC 2019 的开幕式中给我们来带了超多的惊喜,全新的 iPad OS, 给生产力和商业带来了新领域,iOS 项目可以通过简单的修改移植到 Mac OS 上,全新的 Mac Pro 高清的显示器等新硬件,但对于在苹果平台的开发者们,最重要的莫过于 SwiftUI。

曾几何时,iOS 开发者的 UI 开发体验一直是大前端中体验最差的,粗矿原始的 Frame 布局系统, API 冗长难用的 Autolayout , 都是把开发者按在地上使劲的摩擦。 毫无开发体验可言。

去年大火的 Flutter 给客户端上开发带来了全新的体验,声明式的 UI 语法,亚秒级别的实时刷新, 都极大的提升了开发效率,但现在这些都在 SwiftUI.Framework 上得以实现,并且是官方原生的支持。

当天一起观看 WWDC 的小伙伴们都戏称 SwiftUI 真实的名字怕是 AppleFuckFlutter.framework. 作为一个苹果的死粉,当然是第一时间下载 Beta 全家桶尝鲜一下。

注:Xcode11-beta 可以安装在 Mac OS Mojave 上,但是 Preview 功能只能使用在 Mac OS Catalina 上,如果想获得完整体验最好配套升级你的 Mac。



与 Flutter 的开发体验对比


准备好开发工具后我们先来对比下 SwiftUI 和 Flutter 的写法上的直观体验。


是不是非常相似,相似的声明式布局语法,Debug 时期的 Preview 和 Live Reload ,但这次我们不再借助与第三方,Follow 苹果官方。下面从初次体验的方面简单聊一下直观感受。


声明式语法

下面我们看一段简单的声明式语法


SwiftUI

struct ContentView : View {
    var body: some View {
        VStack {
            MapView()
                .edgesIgnoringSafeArea(.top)
                .frame(height: 300)
            
            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)
            
            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack(alignment: .top) {Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
                }
                .padding()
            
            Spacer()
            
        }
    }
}


Flutter

Widget _listItemBuilder(BuildContext context, int index) {
    return Container(
      color: Colors.white,
      margin: EdgeInsets.all(8.0),
      child: Stack(
        children: <Widget>[
          Column(
            children: <Widget>[
              AspectRatio(
                aspectRatio: 16/9,
                child: Image.network(posts[index].imageUrl, fit: BoxFit.cover),
              ),
              SizedBox(height: 16.0),
              Text(
                posts[index].title,
                style: Theme.of(context).textTheme.title
              ),
              Text(
                posts[index].author,
                style: Theme.of(context).textTheme.subhead
              ),
              SizedBox(height: 16.0),
            ],
          ),
          Positioned.fill(
            child: Material(
              color: Colors.transparent,
              child: InkWell(
                splashColor: Colors.white.withOpacity(0.3),
                highlightColor: Colors.white.withOpacity(0.1),
                onTap: () {
                  Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => PostShow(post: posts[index]))
                  );
                }
              ),
            ),
          ),
        ],
      ),
    );
  }
    

观察语法的细节,我们可以注意到一些特征,Flutter 使用典型的声明式语法,开发者声明 UI 的布局方式,一切的布局都交给引擎来解决。在写代码层面 Dart 使用 , 来分割不同的 Widget,这会造成在复杂的布局中,()语法嵌套极其复杂,配上 VSCode 的插件也看的眼花缭乱。

而 Swift 虽然也是声明式语法,但是仔细注意到,Swift 的 View 组合并不是由, 分割,而是由换行分割,在 Swift 中 函数调用是可以换行分割的。这样的 DSL 对开发者的体验更为友好,推测 SwiftUI 使用了类似标记的特征,在统一的时机去做布局。

这样可以做到非常清晰的可读性,并且代码长度也大大缩短。不信你看 Xcode11 终于支持了 miniMap ,在 Objective-C 时代我们的显示器是没有足够空间给我们显示 MiniMap 的。(2019年6月12日更新,Swift 的 DSL 使用 ViewBuilder 构建,是一个特殊的语法糖,本质上和 Flutter 一样)。



但是 Xcode 有一点做的是不如 Flutter 的代码格式化功能非常差,不过相信这点可以通过插件弥补。


Live reload


曾几何时客户端上的开发同学有多么羡慕前端开发同学的 Live Reload ,尤其是 iOS 平台,动辄链接 5 分钟,极大的影响了开发效率,这次 苹果官方 给开发者带来了此项功能。

但 One More Thing ,在 Xcode 中不仅仅可以通过代码改变实时预览,还可以通过编辑预览生成代码, This is amazing。

想象一下 在业务开发后期 UED 同学和你校对视觉的时候是不是可以直接编辑 UI 生成代码 ,而不需要重新编译。

Flutter 的 Live Reload 功能只能在设备上运行时才能工作,但是 SwiftUI 的 Preview 功能默认是和 Xcode 深度集成。

我们可以通过 Group 功能同时预览多个设备,多个不同的环境,涉及到多设备时要强大于 Flutter。


struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE", "iPhone XS Max", "iPad mini 4", "Mac", ].identified(by: \.self)) { deviceName in
            LandmarkList()
                .preferredColorScheme(.dark)
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
            .previewLayout(.sizeThatFits)
            
        }
    }
}

SwiftUI 可以在 Xcode 里面直接切换 LiveMode 可以不运行设备直接进入交互模式,再具有多个预览设备时可以很方便的动态调试 UI 布局。


Data Flow

Flutter 使用 StateFullWidget 的 setState 回掉,通知 Flutter 框架刷新,但是对于真实的业务场景,数据流的管理是一个复杂的问题,官方建议使用 BLOC 和 Redux ScopedModel 加上比较流行的 RXDart 框架控制业务数据的单向流动。 但是在 SwiftUI 中,官方使用 Swift 5.1 带来的新的语法糖 (Property Warpper)来定义,可读性提示非常明显,且有官方维护的 @BindingObject 和 Combine 框架支持。来自业务的数据流动会比 Flutter 更为清晰。




Mix With UIKit


任何一门新技术,对于当前的技术其实都是一次冲剂,对于旧的技术 虽然经过了很多年的历史沉淀,有很多的积累,但是这些积累同时变成了包袱,如何背着包袱负重前行,是任何一门新技术都要考虑的问题, 显然 Swift UI 也考虑到了,目前官方给出的文档中, SwiftUI 是可以和 UIKit 原有的体系很轻松的混合在一起。让开发者可以渐进式的接入 SwiftUI。

Older iOS Version


官方声称 SwiftUI 目前仅支持 iOS 13.x 以上,很多 APP 目前还在兼容 iOS 9 ,看起来用上 Swift UI 还需要 4 年,但是观察今年 苹果的重大改变,包括, iOS 12 以下 蜂窝网络下载可以大于 200M , 苹果官方包优化大小 减少 50% ,iOS 13 以上甚至完全不限制在蜂窝网络下下载的大小,有理由相信 苹果可以考虑把 SwiftUI 内置在 APP 包内,使开发者可以更轻易地兼容低版本的操作系统。


Swift On All Apple Platform


苹果今年推出 SwiftUI 的口号是 The shortest path to building great apps on every device, SwiftUI 提供的 View 架构在 APPKit UIKit TVKit WatchKit 都有对应的视图实现,苹果还指出没有一种写法可以适应所有的设备,要充分发挥各平台的特色。


但是学习了 SwiftUI 可以通过简单的适配到所有的平台,不同于 Reactive Native 的 Learning once, Write anywhere 和 Weex 的 Write Once, Run Everywhere 。苹果充分考虑到用户的实际体验,Apple TV 大屏的体验和 Apple Watch 的便携体验差异巨大,因此 SwiftUI 的理念是 Learn once,Apply anywhere ,各个平台有各个平台的特色实现,但在 SwiftUI 层的 API 写法仍然是一致的。



生态畅想


从上面中可以看到 SwiftUI 做的很多事情和 Flutter 太过相似,目前 Swift UI 支持 Apple 全平台. 但是要知道的是 安卓是开源的 iOS 是闭源生态,Flutter 把手伸到 iOS 平台不太容易,但 SwiftUI 把手伸到安卓平台可就容易多了。



希望这张图有机会变成




参考文档

WWDC2019KeyNote

(developer.apple.com/vid)

Platforms State of the Union

(developer.apple.com/vid)

Introducing SwiftUI: Building Your First App

(developer.apple.com/vid)

ninghao_flutter

(github.com/ninghao/ning)

FlutterDev

(flutter.dev/)


——————————————————————————————————

本篇回答内容来自于阿里巴巴淘系技术部高级开发工程师倾寒。

本账号主体为阿里巴巴淘系技术,淘系技术部隶属于阿里巴巴新零售技术事业群,旗下包含淘宝技术、天猫技术、农村淘宝技术、闲鱼、躺平等团队和业务,是一支是具有商业和技术双重基因的螺旋体。

刚刚入驻知乎,将会给大家带来超多干货分享,立体化输出我们对于技术和商业的思考与见解。

详情介绍可以看这里 阿里巴巴淘系技术介绍

欢迎收藏点赞关注我们!共同进步~ :)