收錄:原文地址
原作者:姜沂(傾寒)
前言
SwiftUI 是蘋果公司于 2019 年推出的 Apple Platform 的新一代聲明式布局引擎灿椅,筆者于去年第一時(shí)間升級(jí) Beta 嘗鮮全家庭,并在短時(shí)間內(nèi)迅速落地了基于 SwiftUI 的內(nèi)部 APP正蛙, 也分享了幾篇關(guān)于 SwiftUI 的文章抚太, 但 SwiftUI 1.0 基本沒有任何公司敢用在正式上線的主 APP 上栋荸,API 在 Beta 版本之間各種廢棄,UI 樣式經(jīng)常不兼容凭舶,大列表性能差,彼時(shí)都標(biāo)識(shí)著 SwiftUI 還稱為一個(gè) Toy Framewrok.
隨著 WWDC 20 相關(guān)新特性和介紹視頻的釋出爱沟,都明確的宣告著 SwiftUI 元年已經(jīng)到了帅霜,SwiftUI 已經(jīng)成長(zhǎng)為新時(shí)代的布局引擎。
以下從幾個(gè)方面分享關(guān)于 SwiftUI 的重大改變及核心優(yōu)勢(shì)呼伸。
PS: 需要讀者對(duì) Swift 及 SwiftUI 1.0 有一定熟悉身冀。
SwiftUI Apps
蘋果在最近幾年的動(dòng)作中一直在搞 Apple Platform 統(tǒng)一的事情钝尸,從最近幾年的 iPad 多任務(wù) 多窗口,到 Mac Catalyst 再到今年更進(jìn)一步直接推出了 Apple silicon 芯片更是從硬件上做到了真正統(tǒng)一(話外音:你們?cè)谲浖贤娴哪切┛缙脚_(tái)的都是小玩意搂根,硬件才是王道)珍促。
還提供了 Rosetta2 Universal2 幫助開發(fā)者基本無成本的遷移到新平臺(tái)上。但是作為軟件工程師還是要更多的關(guān)注軟件生態(tài)的變化剩愧。首先了解下創(chuàng)建 APP 時(shí)的變化
可以看到創(chuàng)建新工程時(shí)有了一套全新的模板基于 SwiftUI App Lifecycle 的跨平臺(tái)項(xiàng)目猪叙。
代碼也從原本的基于 UI/NS HostViewController 變成了基于 APP 的聲明式描述,下面是代碼的前后對(duì)比.
- 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 標(biāo)記了應(yīng)用程序的入口點(diǎn)仁卷,更多請(qǐng)參看 SE-0281-main-attribute.md
乍看好像只有代碼精簡(jiǎn)了不少穴翩,很多人會(huì)認(rèn)為這個(gè)簡(jiǎn)潔程度還不如Flutter 的 main() => runApp(MyApp());.
但最重要的變化是這是第一次跨平臺(tái)代碼,完全無需引入任何 UIKit APPKit WatckKit 等相關(guān)Framewok, 即可直接運(yùn)行在不同平臺(tái)上锦积。這意味著我們后續(xù)在UI布局系統(tǒng)上可以逐漸擺脫對(duì)傳統(tǒng)命令式 UI 編程的依賴芒帕。達(dá)到真正的平臺(tái)無關(guān)。
SwiftUI 將整個(gè)原有的平臺(tái)差異部分抽象為 App 和 Scene丰介,對(duì)于一個(gè) mac/iOS/iPad/watch/tv/..應(yīng)用背蟆,來說 App 代表了整個(gè)應(yīng)用,Scene 代表了與 Window 相關(guān)的多窗口哮幢,有些設(shè)備只有一個(gè) Scene 有些則有多個(gè)带膀,雖然不同的 OS 確實(shí)存在差異,但是在語義層面達(dá)到了一致家浇。
其次一個(gè)沒有歷史包袱的 APP本砰,也可以完整的從 Swift APP lifecycle 風(fēng)格式的模板開始,無需再和傳統(tǒng)的 UIKit/APPKit 等混合钢悲。這也意味著可以達(dá)到 APP 完全 Declared and State-Driven点额。
Viusal Editing
Preview
在傳統(tǒng)的利用 DSL 可視編程框架或者平臺(tái),諸如 Web Flutter 等技術(shù)莺琳,都是開發(fā)者編寫好對(duì)應(yīng)的代碼还棱,運(yùn)行在對(duì)應(yīng)的平臺(tái)或者調(diào)試工具上。SwiftUI 作為蘋果最重要的軟件層戰(zhàn)略框架惭等,更是和 Xcode 深度結(jié)合珍手,在運(yùn)行之前就可以完整的預(yù)覽你所編寫的界面。
強(qiáng)大的 Preview 可以讓你既可以從編寫 DSL 到立即預(yù)覽效果辞做,也可從預(yù)覽的 Canvas 畫布中直接修改效果在代碼編輯器中生成代碼琳要,這對(duì)于日常開發(fā)的效率有非常大的提高,尤其是在 UI 微調(diào)時(shí)秤茅,效果尤為突出稚补。
Xcode12 可以在 Canvas 上同時(shí)預(yù)覽多個(gè)不同設(shè)備環(huán)境的界面,也可以直接投射到真實(shí)的設(shè)備上來預(yù)覽。
對(duì)于日常開發(fā)來說框喳,編寫一個(gè)UI界面通常依賴外部的網(wǎng)絡(luò)/磁盤/其他數(shù)據(jù)课幕,才能正常的構(gòu)建厦坛,這也造成了UI開發(fā)雖然是開發(fā)中較為簡(jiǎn)單的一步,但同時(shí)也是最耗時(shí)的一步乍惊,有了預(yù)覽功能杜秸,可以把很多繁瑣的工作前置解決掉,對(duì)于研發(fā)效率會(huì)有非常大的提高润绎。
Xcode Library
在編寫真實(shí)項(xiàng)目中撬碟,一個(gè)公司的 APP UI 包含成百上千種風(fēng)格的 View 組件,對(duì)于 UI 組件豐富的產(chǎn)品凡橱,如果一個(gè)新需求可以由現(xiàn)有的組件組合小作,那么需求交付的時(shí)間也會(huì)大大縮短。
但是對(duì)于一個(gè)大型的開發(fā)團(tuán)隊(duì)而言稼钩,一個(gè)開發(fā)同學(xué)是很難知道公司內(nèi)到底有多少種組件庫(kù)顾稀,而且即便知道有某種組件庫(kù),開發(fā)同學(xué)初期看到的也是代碼坝撑,一般需要書寫一定的 Demo 才可以用眼睛感知到這個(gè)組件到底是否是我想要的静秆。
在 Xcode 12 中提供了更強(qiáng)大的工具,一個(gè)自定義組件巡李,只需要遵守一個(gè) LiberyContentProvider 協(xié)議就可被Xcode識(shí)別抚笔,可以像系統(tǒng)控件一樣直接從 Xcode 里面識(shí)別并預(yù)覽。對(duì)于一個(gè)大型團(tuán)隊(duì)來說侨拦,此功能可以大大提高找尋組件和查看組件樣式的效率殊橙。
// 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 上也更富有表現(xiàn)力狱从, Swift 支持了多重尾閉包語法和在 ViewBuilde 里面支持 Switch Case 語句膨蛮。
Multiple Trailing Closures
雖然社區(qū)對(duì)多重尾閉包的討論上一直存在爭(zhēng)議問題,但最終 Swift5.3 還是接受并實(shí)現(xiàn)了季研,在普通命令式編程的地方使用會(huì)有一定的困惑性敞葛,但是在 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
在使用傳統(tǒng)命令式編程編寫 UI 代碼時(shí)惹谐,開發(fā)者需要手動(dòng)處理 UIView 和 數(shù)據(jù)之間的依賴關(guān)系,每當(dāng)一個(gè) UIView 使用了外部的數(shù)據(jù)源驼卖,就表明了 UIView 對(duì)外部的數(shù)據(jù)產(chǎn)生了依賴氨肌,當(dāng)一個(gè)數(shù)據(jù)產(chǎn)生變化時(shí),如果意外的沒有同步UIView的狀態(tài)酌畜,那么 Bug 就產(chǎn)生了儒飒。
處理簡(jiǎn)單的依賴關(guān)系是可控的,但是在真實(shí)項(xiàng)目中檩奠,視圖之間的依賴關(guān)系是非常復(fù)雜的桩了,假設(shè)一個(gè)視圖只有 4 種狀態(tài),組合起來就有 16 種埠戳,再加上時(shí)序的不同井誉,情況就更加復(fù)雜。
人腦處理狀態(tài)的復(fù)雜度是有限的整胃,狀態(tài)的復(fù)雜度一旦超過人腦的復(fù)雜度颗圣,就會(huì)產(chǎn)生大量的 Bug,并且修掉了這個(gè)產(chǎn)生了新的Bug屁使。
那么 SwiftUI 是如何解決這個(gè)問題的在岂?
SwiftUI 的框架提供了幾個(gè)核心概念:
統(tǒng)一的 body 屬性,SwiftUI 自動(dòng)從當(dāng)前 App 狀態(tài)集自動(dòng)生成基于當(dāng)前狀態(tài)的快照 View蛮寂。
統(tǒng)一的數(shù)據(jù)流動(dòng)原語蔽午。
關(guān)于 SwiftUI 中的 Data Flow 是如何消除視圖和狀態(tài)不一致的,請(qǐng)參考去年撰寫的文檔 系列文章《深度解讀SwiftUI 背后那些事兒》
今年 SwiftUI 2.0 新增的 StateObject 數(shù)據(jù)流原語讓 SwiftUI 在重復(fù)創(chuàng)建 View 時(shí)避免重復(fù)創(chuàng)建 ObservedObject 從而提高 View 重建的性能酬蹋。
SceneStorage 和 APPStorgae 讓一些可持久化的數(shù)據(jù)變得更加簡(jiǎn)單且具有語義化及老。
New Controls
前面提到的,新增的 DSL 語法 SwiftUI App Lifecycle范抓,以及 Xcode Library Preview 其實(shí)本質(zhì)上都是對(duì)去年 SwiftUI 1.0 錦上添花的新擴(kuò)展骄恶。
真正重要的是今年新增的各類新控件,其中通過導(dǎo)出來自 Xcode11.5 和 Xcode12.0 beta 版本的 Swift 聲明文件匕垫,可以觀察到整個(gè)聲明文件從原來的 10769 行增加到 20564行僧鲁。
新增了約 87 個(gè) struct 16 個(gè) protocol。有了這些豐富的組件才可以更好的構(gòu)建我們的 APP 象泵。
大列表組件
在任何一款 APP 中都會(huì)存在類似大列表組件寞秃,如淘寶 APP 里面的某家店鋪里面商品列表流,首頁(yè)的信息流单芜,都是具有超長(zhǎng)內(nèi)容的列表頁(yè)數(shù)據(jù)蜕该。對(duì)于長(zhǎng)列表頁(yè)來說,過長(zhǎng)的 UI 頁(yè)面會(huì)導(dǎo)致過多的內(nèi)存占用洲鸠,在用戶的設(shè)備中堂淡,內(nèi)存是最為重要的指標(biāo),對(duì)于目前國(guó)內(nèi)的 APP 市場(chǎng)扒腕,低端手機(jī)仍然占據(jù)大量的市場(chǎng)绢淀,對(duì)于這些設(shè)備來說,一旦內(nèi)存超標(biāo)瘾腰,APP 就很容易 OOM皆的,這會(huì)導(dǎo)致用戶體驗(yàn)非常差,在現(xiàn)有競(jìng)爭(zhēng)關(guān)系激烈的市場(chǎng)環(huán)境下蹋盆,體驗(yàn)差意味著會(huì)失去用戶费薄。
對(duì)于傳統(tǒng)的命令式編程來說硝全,我們可以主動(dòng)控制 UITableViewCell 的重用,自建緩沖池等一系列手段去優(yōu)化我們的 APP 內(nèi)存占用楞抡,但是對(duì)于 SwiftUI 1.0 來說伟众,系統(tǒng)提供的控件并沒有有效的辦法去讓我們控制頁(yè)面的渲染,對(duì)于大列表頁(yè)面就容易出現(xiàn)內(nèi)存占用過高的問題召廷。
SwiftUI 2.0 推出了 LazyHStack 和 lazyVStack 加上 List 渲染模式默認(rèn)就是 Lazy 的直接解決了最大的性能問題凳厢。
筆者以去年使用 SwiftUI 編寫的 Emas App 為例,當(dāng)列表頁(yè)(并無大圖)加載到 500個(gè)時(shí)竞慢, APP 使用內(nèi)存已經(jīng)達(dá)到了將近 360MB 先紫。而只需要切換到 Xcode12 API 調(diào)整為到 LazyVStack 內(nèi)存占用直接降低 300MB 。
Widget and Clips
蘋果與 WWDC 20 推出的 WidgetKit 支持的 API 是 SwiftUI Only筹煮,雖然已經(jīng)可以混合部分UIkit 里面的View遮精,但相信沒有歷史包袱 最低支持版本為 iOS14 的 Widget 沒有人會(huì)選擇笨重的命令式 API。
同理 Clips 也一樣寺谤。
這里因?yàn)槠蚓筒蛔稣归_仑鸥,后續(xù)會(huì)有專門的文章分析相關(guān)技術(shù)。
Swift & SwiftUI 的機(jī)會(huì)在哪里变屁?
筆者曾經(jīng)在公司推動(dòng)集團(tuán)升級(jí)了基建眼俊,支持了 Swift 開發(fā)環(huán)境也在淘寶落地了一些場(chǎng)景,但是集團(tuán)內(nèi)一直有一些質(zhì)疑的聲音粟关, 引入 Swift 到底有什么用疮胖?
SwiftUI 又是 N 年后才可以用上的小玩意,Objective-C 不夠用嗎闷板?現(xiàn)在筆者可以回答這些質(zhì)疑的聲音澎灸, Swift 未來的機(jī)會(huì)在 效率,體驗(yàn)和蘋果的技術(shù)紅利遮晚。
效率
從研發(fā)效率上來說性昭, Swift 對(duì)比 Objective-C 的精簡(jiǎn)程度不言而喻,筆者在淘寶 APP 上線的模塊代碼量下降了 40 %县遣。 但更進(jìn)一步糜颠,如果編寫 UI 界面從 UIKit 轉(zhuǎn)向了 SwiftUI 代碼量直接少了不止一倍。更少的代碼意味著更快的交付萧求,在目前競(jìng)爭(zhēng)激烈的市場(chǎng)會(huì)有更多的試錯(cuò)場(chǎng)景其兴。關(guān)于使用 UIKit 編寫代碼轉(zhuǎn)向 SwiftUI 的代碼量對(duì)比,讀者可以參考開源 APP MovieSwiftUI 直觀了解夸政。
體驗(yàn)
讀者可能比較困惑對(duì)于切換語言和框架元旬,對(duì)體驗(yàn)看上去沒有任何幫助,但事實(shí)真是這樣嗎?
首先引入 Swift 后匀归,由于 Swift 語言設(shè)計(jì)之初便對(duì)安全性列為最重要的目標(biāo)坑资,Swift的引入會(huì)讓代碼盡可能的減少未定義的行為,減少 Crash 意味著APP的穩(wěn)定性提高穆端,體驗(yàn)自然更佳盐茎。
其次雖然 Swift 同樣的語言出于對(duì)安全性考慮編譯處理的指令會(huì)比 Objective-C 更多,但是如果UI部分都用 SwiftUI 來寫呢徙赢?
更少的代碼意味者更小的包大小,目前國(guó)內(nèi)巨頭 APP iOS 端 APP 包大小都朝著 200 MB 奔去探越,如果能減少更多的代碼對(duì)包大小也可以在 200MB 的限制下承載更多而業(yè)務(wù)狡赐。對(duì)用戶的體驗(yàn)也有較大的提升。
更進(jìn)一步由于 Swift 選擇使用值類型構(gòu)建整個(gè)APP钦幔,值類型的有點(diǎn)在于更扁平化的內(nèi)聯(lián)數(shù)據(jù)結(jié)構(gòu)去分配內(nèi)存枕屉,而不是使用更多間接指針引用,減少了大量不必要的堆內(nèi)存消耗鲤氢,意味著整體內(nèi)存使用量的降低搀擂。對(duì)整個(gè) APP 的穩(wěn)定性也有較大的提高。
? 蘋果的選擇
Swift 做為蘋果的戰(zhàn)略語言已經(jīng)發(fā)展的越來越壯大卷玉,自 2019 年 Swift ABI 穩(wěn)定后哨颂,蘋果在 Swift 的投入越來越大。我們可以進(jìn)入 /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, 以及針對(duì) 144 個(gè)公開 Framework威恼,根據(jù) Swift Style 重新設(shè)計(jì)了 57 個(gè) Framework 的API。
從以下數(shù)據(jù):
從 WWDC17 后 蘋果已經(jīng)不再使用 Objective-C 做 Sample Code 演示
https://developer.apple.com/不再更新 Objective-C 相關(guān)的文檔
WidgetKit 是 SwiftUI only寝并。
App Clips 10M的包大小箫措, SwiftUI 是最合適的框架
開源社區(qū)逐步放棄 Objecive-C 如 Lottie。
可以判斷衬潦,Swift 是未來 Apple 平臺(tái)的唯一選擇斤蔓,越是有包袱的大廠 APP,從現(xiàn)在還不盡早儲(chǔ)備镀岛,在未來越會(huì)寸步難行弦牡。
我們需要做些什么?
? Swift我們已經(jīng)做了什么
一套支持 Swift 二進(jìn)制的研發(fā)環(huán)境
300+ 支持了混編的淘系 SDK哎媚。
手淘落地了 6 個(gè)模塊喇伯。
集團(tuán)新增了約 20個(gè) 支持 Swift 的APP。
10 多場(chǎng)技術(shù)培訓(xùn)拨与。
169+ 語雀知識(shí)沉淀稻据。
300+ 工程師的集團(tuán) Swift 官方組織。
2 個(gè) 技術(shù)創(chuàng)新產(chǎn)品
經(jīng)過去年橫向組織大家共同的努力,我們已經(jīng)已經(jīng)支持了橫向大基建捻悯。包括研發(fā)環(huán)境匆赃,工具支撐,沉淀了大量的文檔今缚,還有相關(guān)的技術(shù)課程算柳。
要朝什么方向去努力
目前集團(tuán)對(duì)于 Swift 的呼聲越來越高,我們大量的工程師希望的去使用 Swift 姓言。
目前首先要做的事情是依托 Swift 和 SPM 提升我們的開發(fā)體驗(yàn)瞬项,升級(jí)我們的中間件,使業(yè)務(wù)可以大量的用起來 Swift 何荚,提高我們的研發(fā)效率和代碼質(zhì)量囱淋。
升級(jí)基于 SPM 的新的包管理體系
升級(jí)老舊基礎(chǔ)庫(kù),打磨新一代基建餐塘。
引入新的 Swift 特有庫(kù) 賦能業(yè)務(wù)妥衣。
? SwiftUI雖然前文提到了 SwiftUI 的眾多優(yōu)點(diǎn),包括研發(fā)效率戒傻,體驗(yàn)的提高税手,但是在國(guó)內(nèi)的環(huán)境中 SwiftUI 也有它致命的弊端
iOS 14 才可放心的使用。
只支持 Apple Platform需纳,這和國(guó)內(nèi)的要支持 Mobile Platform 從理念上沖突芦倒。
大型 APP 要解決的是如何部署到低版本操作系統(tǒng)上和安卓平臺(tái)上,畢竟很多公司還在支持 iOS 9 對(duì)于升級(jí)到最低支持 iOS 14 好像還需要一個(gè)世紀(jì)那么漫長(zhǎng)候齿,而且國(guó)內(nèi)的設(shè)備占比大頭還是以 Android 巨多 熙暴。
雖然可以看到 Swift 語言也在逐漸支持 Android 平臺(tái),但是也看到蘋果對(duì)于安卓平臺(tái)的 SwiftUI 并沒有太大興趣慌盯。
從體驗(yàn)上 Flutter 遠(yuǎn)不如 SwiftUI 這種親兒子效果好周霉, 但對(duì)于國(guó)內(nèi)跨端欲望旺盛的市場(chǎng)來說 SwiftUI 還是比不過 Flutter, 不過既然 SwiftUI DSL 層已經(jīng)基本固定亚皂,那么也有可能投入人力直接在低版本操作系統(tǒng)上實(shí)現(xiàn)一套自建的 SwiftUI 引擎俱箱,或者將 SwiftUI 引擎移植到安卓平臺(tái),比如對(duì)接 Flutter 或者直接對(duì)接 Android Native灭必。
比起 Flutter 引入雙端帶來的包大小增量和體驗(yàn)不一致的情況狞谱, SwiftUI 保留 iOS 平臺(tái)體驗(yàn),只侵入一端的選擇顯然要更好一點(diǎn)禁漓。
不過短期內(nèi)我們可以在 Clips 和 Widget 場(chǎng)景下開始使用 SwiftUI跟衅, 畢竟 SwiftUI 快速的開發(fā)效率對(duì)和較低的包大小占用非常適合這樣的場(chǎng)景,我們可以在業(yè)務(wù)場(chǎng)景中練兵儲(chǔ)備我們的 SwiftUI播歼,并積極在主 APP 中嘗試伶跷。