WWDC 20 SwiftUI 的重大改变及核心优势

6月23日凌晨 1 点,苹果 WWDC20 开发者大会在线上以主题演讲的方式,在 Apple Park 进行直播。

23-26日,苹果公开了 100 多个面向开发者的视频,内容涵盖Swift / SwiftUI 、App Clips、Widgets、Privacy & Security 等等方面。

对于开发者和程序员来说,我们有哪些新发现和新思考?

前言


SwiftUI 是苹果公司于 2019 年推出的 Apple Platform 的新一代声明式布局引擎,但 SwiftUI 1.0 基本没有任何公司敢用在正式上线的主 APP 上,API 在 Beta 版本之间各种废弃,UI 样式经常不兼容,大列表性能差,彼时都标识着 SwiftUI 还称为一个 Toy Framewrok.随着 WWDC 20 相关新特性和介绍视频的释出,都明确的宣告着 SwiftUI 元年已经到了,SwiftUI 已经成长为新时代的布局引擎。以下从几个方面分享关于 SwiftUI 的重大改变及核心优势。
image

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


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


image

可以看到创建新工程时有了一套全新的模板基于 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。

image

image

Viusal Editing


Preview

在传统的利用 DSL 可视编程框架或者平台,诸如 Web Flutter 等技术,都是开发者编写好对应的代码,运行在对应的平台或者调试工具上。SwiftUI 作为苹果最重要的软件层战略框架,更是和 Xcode 深度结合,在运行之前就可以完整的预览你所编写的界面。 强大的 Preview 可以让你既可以从编写 DSL 到立即预览效果,也可从预览的 Canvas 画布中直接修改效果在代码编辑器中生成代码,这对于日常开发的效率有非常大的提高,尤其是在 UI 微调时,效果尤为突出。 Xcode12 可以在 Canvas 上同时预览多个不同设备环境的界面,也可以直接投射到真实的设备上来预览。
image

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

Xcode Library

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

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。
image
image

那么 SwiftUI 是如何解决这个问题的? SwiftUI 的框架提供了几个核心概念: 1. 统一的 body 属性,SwiftUI 自动从当前 App 状态集自动生成基于当前状态的快照 View。 2. 统一的数据流动原语。
image

今年 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 编写的APP,当列表页(并无大图)加载到 500个时, APP 使用内存已经达到了将近 360MB 。而只需要切换到 Xcode12 API 调整为到 LazyVStack 内存占用直接降低 300MB 。
image
image

Widget and Clips


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


一直有一些质疑的声音, 引入 Swift 到底有什么用? SwiftUI 又是 N 年后才可以用上的小玩意,Objective-C 不够用吗? Swift 未来的机会在 效率,体验和苹果的技术红利。

效率

从研发效率上来说, Swift 对比 Objective-C 的精简程度不言而喻,代码量下降了 40 %。 但更进一步,如果编写 UI 界面从 UIKit 转向了 SwiftUI 代码量直接少了不止一倍。更少的代码意味着更快的交付,在目前竞争激烈的市场会有更多的试错场景。关于使用 UIKit 编写代码转向 SwiftUI 的代码量对比,读者可以参考开源 APP MovieSwiftUI https://github.com/Dimillian/MovieSwiftUI
直观了解。

体验

读者可能比较困惑对于切换语言和框架,对体验看上去没有任何帮助,但事实真是这样吗? 首先引入 Swift 后,由于 Swift 语言设计之初便对安全性列为最重要的目标,Swift的引入会让代码尽可能的减少未定义的行为,减少 Crash 意味着APP的稳定性提高,体验自然更佳。其次虽然 Swift 同样的语言出于对安全性考虑编译处理的指令会比 Objective-C 更多,但是如果UI部分都用 SwiftUI 来写呢? 更少的代码意味者更小的包大小,目前国内巨头 APP iOS 端 APP 包大小都朝着 200 MB 奔去,如果能减少更多的代码对包大小也可以在 200MB 的限制下承载更多而业务。对用户的体验也有较大的提升。更进一步由于 Swift 选择使用值类型构建整个APP,值类型的有点在于更扁平化的内联数据结构去分配内存,而不是使用更多间接指针引用,减少了大量不必要的堆内存消耗,意味着整体内存使用量的降低。对整个 APP 的稳定性也有较大的提高。
image
image

苹果的选择

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/apple 和 https://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,从现在还不尽早储备,在未来越会寸步难行。
image
image

你可能感兴趣的:(WWDC 20 SwiftUI 的重大改变及核心优势)