前言
相信每一個見到 SwiftUI 的開發者,都會立刻將這門船新的 UI 框架和 Flutter 聯絡到一起。是的,它們身上有太多太多相似的地方,相似的宣告語法、實時熱更新、跨平臺(SwiftUI 僅僅跨 Apple 平臺)等等,讓羨慕了前端技術爆發的移動開發圈子也熱鬧了一回。那麼 SwiftUI 和 Flutter 到底有什麼相似和不同?它們又各有什麼優缺點?以及最後,單就技術方向而言,誰才是未來跨平臺方案的贏家呢?
語言
現代計算機語言越發趨於相似是一個不爭的事實,因為每個語言的基本目標都相當的一致:簡潔、靈活、安全、高效能。同時,各種語言的優異特性也都在被相互借鑑,更近一步減少了各個語言之間的鴻溝。
Swift 和 Dart 分別作為這兩個 UI 框架的唯一官方語言,在語法層面的差別其實非常小,網上也有大量的文章來對比這兩種語言的優劣性。我的使用體驗和大部分人相似,就目前而言,Swift 和 Dart 各有優劣。
-
Swift 比 Dart 更加簡潔。Swift 本身在語法層面已經比 Dart 要簡潔很多,比如無需在句末新增
;
分號等。這一點在直接編寫 SwiftUI 和 Flutter 上會顯得尤為明顯。不過這個問題並不全是語言層面帶來的問題,也跟這兩個 UI 框架的設計有關。ForEach(userData.landmarks) { landmark in NavigationButton( destination: LandmarkDetail(landmark: landmark)) { LandmarkRow(landmark: landmark) } } 複製程式碼
SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final landmark = landmarks[index]; return LandmarkCell( landmark: landmark, onTap: () { Navigator.push( context, CupertinoPageRoute( builder: (context) => LandmarkDetail( landmark: landmark, ), ), ); }, ); }, childCount: landmarks.length, ), ), 複製程式碼
兩者都實現了一個可以點選的列表頁,都呼叫了一個自定義 Cell 。
-
Swift 比 Dart 更加嚴格,從某種角度上講會更加安全,當然安全這種事情追根揭底還是得靠人。
-
Swift 在釋出的第5個年頭竟然還沒有 await/async ,雖然早有提案但一直沒有落實下來。不過 Dart 這邊也沒有可選型。只能說兩者互相吸收的還不夠。
-
Dart 的黑科技:同時支援 AOT 和 JIT,這也賦予了 Flutter Web 級的熱過載特性,是 Flutter 對決 SwiftUI 的有力武器。
-
Swift 和 Dart 都是開源語言。Apple 對 Swift 的開放是史無前例的,目前 Swift 已經可以脫離 macOS 執行,同時在社群中也出現了諸如後端框架
Vapor
等一系列有意思的東西,更是成為了Tensorflow
的官方語言之一。這一切大大擴充了 Swift 的使用場景。不過,Dart 在可玩性上明顯走的要比 Swift 更快。 用其開發的後端框架已經被一些公司投入到了生產環境,Google 的 Web 框架AngularDart
(非 Flutter Web)也早已在一些服務上穩定執行了許久。更不用說藉助 Flutter 跑遍 iOS Android macOS Windows Web,簡直可以用所向睥睨來形容。
一個語言的設計好壞的確會影響一個框架的受歡迎程度,我已經聽過無數 Flutter 開發人員吐槽 Dark 無止境的巢狀問題。雖然 Dark 的可玩度更高,但就單 UI 框架而言, 我個人認為 Swift 的使用感受要明顯好於 Dart。
語法
SwiftUI 和 Flutter 都無一例外的使用了宣告式語法和各自的 DSL 來描述 UI ,它的好處是,你能一眼就從程式碼的結構中看出實際 UI 的結構,並且避免反覆的邏輯程式碼。SwiftUI 用資料繫結和狀態管理替代了原有的複雜控制邏輯,而 DSL 又使得程式碼在結構上和 UI 的層級結構高度一致。
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()
}
複製程式碼
上面是 WWDC 上出現的一個 SwiftUI Demo LandMark。即便你沒有學習過 SwiftUI 的語法,稍稍閱讀一下上面的程式碼,你也能發現,它和圖片中的 UI 是一一對應的。這就是宣告式語法的優點。不過在具體寫程式碼的時候,Flutter 和 Swift 的感受就差太多了,最明顯的就是:Flutter 要比 SwiftUI 複雜太多了。上面的效果用 Flutter 實現要寫多少程式碼呢?多到我都沒法貼上過來,否則我的文章有一半都會是 Dart 程式碼。不過這裡有一個人仿照著 Demo 寫了一份 Flutter 版的 LandMarks ,大家可以參考一下。
為什麼同樣是宣告式語法,Fluter 要比 SwiftUI 複雜這麼多呢。這其實體現了 Apple 和 Google 在設計這兩個 UI 框架時的不同思路,甚至代表了這兩家公司的不同文化。對 iOS 和 Android 稍有了解的開發者都知道,iOS 開發要比 Android 開發輕鬆很多。單就配置開發環境來說,iOS 就可以比 Android 提早1天開始寫程式碼。更不容說複雜的框架、混亂的平臺相容性、生澀難懂的文件。
Apple 對待開發者,就像是在對待新手一樣,極盡所能的去簡化開發過程,減少開發中的工作量,甚至還要讓開發變成一件優雅的事情,這從 Xcode 的外觀功能設計到系統 API 再到 Swift 語言都是如此。而 Google 在對待開發者時,更像是對待極客一樣,將大量的工作和創作交給開發人員,讓它們自由的創造有意思的東西。但相應的,它的門檻也很高,而且自由帶來的碎片化和不可控性也會導致開發效率的降低。
在設計 SwiftUI 的 API 時,Apple 隱藏了大量的內部細節,讓開發者呼叫盡可能少的程式碼就能實現相應的效果。開發者不需要重寫初始化方法,不需要使用 return
,不需要要關心 context
上下文,也不需要關心它應該是一個 StatefulWidget
還是 StatelessWidget
, 更不需要關心用的是 Material Design 還是 iOS Style。能做的 Apple 都幫你在內部做好了,你需要做的,只是告訴編譯器,你需要什麼控制元件,它們是什麼樣的,它們應該在什麼地方,僅此而已。如果說 Flutter 給移動開發帶來的宣告式語法讓所有人眼前一亮的話,那麼 SwiftUI 的出現則是告訴所有人(包括 Flutter ),什麼才是真正的宣告式語法。
實時更新
SwiftUI 最為重磅的功能,就是萬眾期待的熱載入,也就是實時更新。Web 端超高的開發效率一直是移動端夢寐以求的技能,RN 的出現讓所有移動開發者第一次看到了這種可能,雖然它如今也有著自己的問題。Flutter 作為 Web 技術的一種延伸,繼承了大量的 Web 技術特性,熱載入似乎也就成為了手到擒來的技能。而 SwiftUI 要想實現近似 Web 那樣的實時重新整理,所要面對的困難要複雜的多。從我的實際體驗下來,也充分說明了這一點。
SwiftUI 的實時預覽分為靜態預覽和動態預覽。預設情況下展示的是靜態預覽,它的速度快,而且支援程式碼和視覺化兩種編寫方式。不過它沒有任何響應事件,無法滾動和跳轉頁面。如果需要動態除錯,則需要切換到動態預覽。動態預覽需要經過一段編譯時間,然後可以以完全動態的形式實時響應 UI 的變化,而且可以在真機上實時除錯。但動態預覽的限制還比較多,無法做到 Flutter 那種只需編譯一次,之後整個 App 都能實現動態更新。
為什麼會有靜態預覽和動態預覽呢?我相信如果不是出於一些問題的考慮(效能問題、難以處理的 Bug),Apple 是斷然不會將這個問題複雜化的。只能說,目前殘疾版的熱載入是 Apple 能給我們的最好效果。正因此,目前 SwiftUI 所謂的實時重新整理,距離 Web 以及 Flutter,還是小兒科。靜態 UI 除錯其實完全可以通過 StoryBoard 來實現,而真正實用的動態預覽又有一定的侷限性。不過 SwiftUI 尚處在測試階段,暫時還無法對其效能作出最終的定論。
效能
Google 宣稱 Flutter 的目標之一就是流暢(60Hz),目前來看,它確實在大部分場景做到了。不過根據我的測試(此時尚處於 beta 版),像頁面轉場、控制元件動畫這樣的場景,跑到 60 Hz 還是有一定壓力。我一直都有在關注 Flutter 專案,我們內部也在嘗試將部分模組使用 Flutter 重寫。但我至今體驗下來的情況,目前的 Flutter, 只能說很接近原生平臺的流暢度。不過即便 Flutter 能夠達到系統原生的流暢度,它也始終無法迴避效能損耗的問題。iOS 的原生 UI 框架(包括 SwiftUI)都是搭建在 Metal 之上的,Metal 對於 GPU 的使用效率遠要高於 OpenGL 。其帶來的結果在我的測試中也表明了,同樣的頁面渲染和動畫,Flutter 對於 CPU 和 GPU 的利用率要高於 iOS 原生 UI 框架。而高效能消耗,對裝置就意味著高發熱和低續航。這在移動裝置上尤為嚴重。
反觀 SwiftIU,Apple 似乎照搬了系統所有的原生控制元件,原則上不會有效能上的改觀,但千萬不要被你的眼睛騙了,Apple 在你看不見的地方,下了一盤大棋。
上面兩張圖分別是 SwiftUI 和 iOS原生渲染的 Label,它們都執行在 iOS 平臺,都長得一摸一樣,但它們背後是兩個完全不一樣的東西。我們發現,SwiftUI 中的文字已經不是 iOS 系統的原生控制元件了,而是一個新的型別,名為 Text
。它其實是一個繼承自 UIView 的新控制元件,全名是 DisplayList.ViewUpdater.Platform.CGDrawingView 。觀察右側的屬性視窗,發現兩個控制元件有著完全不同的屬性。UILabel
有著複雜的屬性,用於控制樣式、互動等。而 Text
則要精簡的多的多。
有傳言說,SwiftUI 的部分控制元件已經拋棄了系統原生 UI 框架,轉而直接使用了更加底層,同時也在 Apple 生態系統中更加通用的 Core Animation、Core Graphics、Core Text 進行渲染。是否直接使用了底層框架進行高效渲染我暫時還無得而知,但目前可以肯定的是,Apple 正在將 SwiftUI 剝離出原有的 UI 框架,丟掉沉重的歷史包袱。這勢必會帶來諸多益處,比如更高的自由度、更好的效能,以及真正意義上的:跨平臺。
跨平臺?
可能很多人都會說,SwiftUI 和 Flutter 根本沒法一起比較,SwiftUI 不是真正的跨平臺。沒錯,SwiftUI 目前所跨的僅僅是 Apple 自家的生態圈,包含了 iOS(iPadOS)macOS watchOS tvOS,而隔壁 Flutter 早已玩膩了移動端,開始向 Windows macOS 以及 Web進發。不過,你不可否認,SwiftUI 確實做到了在完全不同的系統平臺上實現了 Write once,use anywhere。而且,隱藏在這背後的思路還完全不一樣。
SwiftUI 從根本上不是在構建 UI,而是在描述 UI 。這兩者有什麼區別?構建 UI 時,你會明確每一個控制元件的型別,甚至精確到平臺。比如在原生 UI 框架中,你需要一個輸入框 ,你就要根據不同的平臺,選擇具體是使用 UITextFiled(iOS) 還是 NSTextFiled(macOS),而這兩個輸入框 有著不同的屬性、外觀和特性。在 Flutter 中,你不需要考慮不同平臺的控制元件型別,但你需要考慮控制元件的風格,官方提供了符合 Material Design 的 Android 控制元件和 iOS Style 的 iOS 控制元件,它們的許多特性也不一致,這一切給 UI 構建帶來了更加複雜的邏輯和工作量。
而 SwiftUI 更像是 Web 的模版,它只描述 UI ,而控制元件具體長什麼樣子,有什麼特性,會根據編譯的平臺因地制宜的實現,而且是自動的。這就像是給 Web 套上了一個模版,一個主題。
左側的程式碼描述了一個表單,其中包含了一些簡單的控制元件,包括Picker
、Toggle
、Stepper
、Button
。如果我們把程式碼原封不動的放到 macOS 專案中,它會變成下面這個樣子。
你會發現,同樣的程式碼,展示出來的 UI 卻完全不一樣,但 UI 表達的內容確實完全一致的。更令人細思極恐的是,Picker
這個"單項列表選擇" 控制元件,在iOS上被翻譯成了一個選擇頁面,點選 cell 會 push 出所有可選擇的選項,點選選項會返回上一頁面並將選中的內容展示到對應的 cell 上。而在 macOS 上,Picker
則會被翻譯成我們桌面作業系統上常見的下拉選單。這種轉變是不可思議的,我只能用 Wow、Awesome、Amazing 來形容它。因為它完全符合平臺設計語言和使用者使用習慣的,同時又極大的降低了開發和適配難度。然而 SwiftUI 能做的還遠遠不止這些。
沒錯,不僅僅是 iOS 和 macOS,在 tvOS 和 watchOS 上,SwiftUI 也將以最符合平臺設計語言和互動的方式去展示每一個控制元件。而且,你還將自動獲得大量的系統特性,比如 Dynamic Type、Dark Model、閱讀方向自適應等。
從這一點上來看,SwiftUI 和 React Native 有著更為相似的思路和技術。只不過 Facebook 無論對 iOS 還是 Android 都幾乎沒有任何話語權,因此在相容性、一致性上都有一定問題。而為了達到這種相容性,不僅 React Native 自身需要做大量的工作,開發人員也需要疲於應對各種問題,可以說是一種比較尷尬的中間技術。
而 SwiftUI 和 React Native 與 Flutter 的跨 UI 平臺思想有著本質的不同。Flutter 完全拋棄了執行平臺的原生 UI 框架,類似於在一張畫布上一個畫素一個畫素"畫"出每一個控制元件,類似於一個 2D 遊戲引擎。它的目的也很明確:確保相同的程式碼在不同的作業系統、硬體裝置、螢幕尺寸下展示完全相同的 UI 。這似乎是開發者想要的,因為我們受夠了 RN 在不同平臺上表現出的不可控差異。但即便拋去了不同作業系統設計語言的差異,不同尺寸螢幕下的 UI 至少應該是很不一樣的。桌面平臺 UI 和移動平臺 UI,甚至手錶上的 UI,應該根據使用者的使用習慣和操控方式,制定完全不同的 UI 和互動。如果要在 Flutter 上實現多平臺的適配,結果對任何一個程式設計師而言都可能是一場噩夢。你需要做大量的判斷,並且可能需要使用多種控制元件來實現單一功能,最終還要面對大量的測試和 Bug。
不過有利就有弊。SwiftUI 近乎完美的跨平臺方案是建立在較低自由度下的。通過上面的例子你也發現了,Demo 中使用的都是系統原生的控制元件樣式。這並不是說我們無法自定義,只不過:
- 我們能夠自定義的專案不夠多
- 我們自定義的內容越多,系統為我們自動新增的特性失去的也就越多
比如我們想要修改列表背景的顏色,那麼我們將會失去部分系統自動為我們新增的 Dark Model 特性,你需要做一些額外的工作來告訴 App 在黑/白模式下背景應該展示什麼顏色。
但好在 Apple 已經開始嘗試脫離系統原有的 UI 框架,這反而讓我們更加方便自定義部分 UI 。以往我們要實現下面這個 Button 的樣式,需要對 UIButton 的引數進行大量調整,因為預設的 UIButton 是文字在左,圖片在右。如今,SwiftUI 脫離了系統 UI 框架組建後,我們可以通過極其簡單的程式碼實現複雜的 UI 樣式。
不過這和 Flutter 引以為傲的 "精確到畫素" 還有一定差距,我們可能無法像 Flutter 那樣完全掌控 UI 控制元件的所有細節。但就像畫畫一樣,你失去了一定的自由,換來的是更快更高效的開發,這恰恰體現了 Apple 的企業風格。
然而 SwiftUI 跨平臺的故事講完了嗎?我認為這恰恰只是個開始。SwiftUI 這個完全與平臺、裝置無關的,純描述的 UI 框架,恰恰才是跨平臺方案的正確方向。SwiftUI 能夠用來描述 iOS macOS tvOS 甚至 watchOS 的 UI,為什麼不能用來描述 Android,或者 Web?SwiftUI 已經擁有了 Flex 佈局、combine 資料繫結、熱載入等一系列特性,我們只需要照著這個思路,將 Android 或者 Web 的模版套用在 SwiftUI 上,就能同樣得到符合平臺設計語言和規範的 UI 。當然,這遠沒有我說的那麼簡單,但是不要忘了,Swift 是開源的,跨平臺執行也不是問題。只要條件存在,一切皆有實現的可能。
然而到那時,Flutter 又會變成什麼樣子呢?至少在目前,Flutter 還是會成為大部分公司跨平臺的首要方案之一,而 SwiftUI 嘛,大面積使用至少也是2~3年之後的事咯。