聊聊 Swift 中的熱重載
?前言
這一年是2040年,我們最新的 MacBook M30X 處理器可以感知到瞬間編譯大型 Swift 項(xiàng)目,聽起來很神奇,對吧?除此之外,編譯代碼庫只是我們迭代周期的一部分。包括:
- 重新啟動它(或?qū)⑵洳渴鸬皆O(shè)備)
- 導(dǎo)航到您在應(yīng)用程序中的先前位置
- 重新生成您需要的數(shù)據(jù)。
如果您只需要做一次的話,聽起來還不錯。但是如果您和我一樣,在特別的一天中,對代碼庫進(jìn)行 200 - 500 次迭代,該怎么辦呢?它增加了。
有一種更好的方法,被其他平臺所接受,并且可以在 Swift/iOS 生態(tài)系統(tǒng)中實(shí)現(xiàn)。我已經(jīng)用了十多年了。
從今天開始,您想每周節(jié)省多達(dá) 10 小時的工作時間嗎?
熱重載
熱重載是關(guān)于擺脫編譯整個應(yīng)用程序并盡可能避免部署/重新啟動周期,同時允許您編輯正在運(yùn)行的應(yīng)用程序代碼并且能立即看到更改。
這種流程改進(jìn)可以每天為您節(jié)省數(shù)小時的開發(fā)時間。我跟蹤我的工作一個多月,對我來說,每天節(jié)省了 1-2 小時。
坦白地說,如果每周節(jié)省10個小時的開發(fā)時間都不能說服您去嘗試,那么我認(rèn)為任何方法都不能說服你。
其他平臺在做什么?
如果您只使用 Apple 平臺,您會驚訝地發(fā)現(xiàn)有好多平臺幾十年前已經(jīng)采用了熱重載。無論您是編寫 Node 還是任何其他 JS 框架,都有一個使用熱重載的設(shè)置。Go 也提供了熱重載(本博客使用了該特性)
另一個例子是谷歌的 Flutter 架構(gòu),從一開始就設(shè)計用于熱重載。如果您與從事 Flutter 工作的工程師交談,你會發(fā)現(xiàn)他們最喜歡 Flutter 開發(fā)者體驗(yàn)的一點(diǎn)就是能夠?qū)崟r編寫他們的應(yīng)用程序。當(dāng)我為《紐約時報》寫了一個拼字游戲時,我很喜歡它。
微軟最近推出了 Visual Studio 2022,并為 .NET 和 標(biāo)準(zhǔn) C++ 應(yīng)用程序提供熱重載,在過去的十年中,微軟在開發(fā)工具和經(jīng)驗(yàn)方面一直在大殺四方,所以這并不令人驚訝。
蘋果生態(tài)系統(tǒng)怎么樣?
早在 2014 年推出時,很多人都對 Swift Playgrounds 感到敬畏,因?yàn)樗鼈冊试S我們快速迭代并查看代碼的結(jié)果,但它們并不能很好地工作,因?yàn)樗嬖诒罎?、掛起等問題。不能支持整個iPad環(huán)境。
在它們發(fā)布后不久,我啟動了一個名為 Objective-C Playgrounds 的開源項(xiàng)目,它比官方 Playgrounds 運(yùn)行得更快、更可靠。我的想法是設(shè)計一個架構(gòu)/工作流程,利用我已經(jīng)使用了幾年的 DyCI 代碼注入工具,該工具已經(jīng)由 Paul 制作。
自從 Swift Playgrounds 存在以來,已經(jīng)過去了八年,而且它們變得更好了,但它們可靠嗎?人們是否在使用它們來推動開發(fā)?
以我的經(jīng)驗(yàn):并非如此。Playgrounds 在大型項(xiàng)目中往往不太可靠或適用。
SwiftUI 出現(xiàn)了,它是一項(xiàng)了不起的技術(shù)(盡管仍然存在錯誤),它引入了與 Playgrounds 非常相似的 Swift Previews 的想法,它們有什么好處嗎?
類似的故事,當(dāng)它工作的時候是很好的,但是在更大的項(xiàng)目中,它的工作是不可靠的,而且往往中斷的次數(shù)比它們工作的次數(shù)多。如果你有任何錯誤,他們不會為你提供調(diào)試代碼的能力,因此,采用的情況有限。
我們需要等待 Apple 嗎?
如果你關(guān)注我一段時間,你就已經(jīng)知道答案了,絕對不要。畢竟,我的職業(yè)生涯是構(gòu)建普通 Apple 解決方案無法解決的問題:從像 Sourcery 這樣的語言擴(kuò)展、像 Sourcery Pro 這樣的 Xcode 改進(jìn),再到 LifetimeTracker 以及許多其他開源工具。
我們可以利用我最初在 2014 Playgrounds 中使用的相同方法。我已經(jīng)使用它十多年了,并且在數(shù)十個 Swift 項(xiàng)目中使用它并取得了巨大的成功!
許多年前,我從使用 DyCI[1] 切換到 InjectionForXcode,通過利用 LLVM 互操作而不是任何 swizzling ,它的效果更好。它是一個完全免費(fèi)的開源工具,您可以在菜單欄中運(yùn)行,它是由多產(chǎn)的工程師 John Holdsworth 創(chuàng)建的。你應(yīng)該看看他的書 Swift Secrets[2]。
我意識到 Playgrounds 的方法可能過于笨重,所以今天,我開源了。一個非常專注的名為 Inject 的微型庫,與 InjectionForXcode 搭配使用時,將使您的 Apple 開發(fā)更加高效和愉快!
但不要只相信我的話??纯?Alexandra 和 Nate 的反饋,在我將這個工作流程引入 The Browser Company 設(shè)置之前,他們已經(jīng)非常精通了,這使得它更加令人印象深刻。
Inject
這個小型庫是完全通用的,無論您使用 UIKit?、 AppKit? 還是 SwiftUI,您都可以使用它。
您無需為生產(chǎn)應(yīng)用程序添加條件或刪除 Inject 代碼。它變成了無操作內(nèi)聯(lián)代碼,將在非調(diào)試版本中被編譯過程剝離。您可以在每個視圖中集成一次,并持續(xù)使用數(shù)年。
請參考 GitHub repo[3] 中關(guān)于配置項(xiàng)目的說明。現(xiàn)在讓我們來看看您有哪些工作流程選項(xiàng)。
工作流
SwiftUI
只需要兩行字就可以使任何 SwiftUI 啟用實(shí)時編程,而當(dāng)您這樣做時,您將擁有比使用 Swift Previews 更快的工作流程,同時能夠使用實(shí)際的生產(chǎn)數(shù)據(jù)。
這是我的 Sourcery Pro[4] 應(yīng)用程序的示例,其中加載了我所有的實(shí)際數(shù)據(jù)和邏輯,使我能夠即時快速迭代整個應(yīng)用程序設(shè)計,而無需任何重新啟動、重新加載或類似的事情。
看看這個開發(fā)工作流程有多快吧,告訴我你寧愿在我每次接觸代碼時等待Xcode的重新構(gòu)建和重新部署。
UIKit / AppKit
我們需要一種方法來清理標(biāo)準(zhǔn)命令式UI框架的代碼注入階段之間的狀態(tài)。
我創(chuàng)建了 Host 的概念并且在這種情況下工作的很好。有兩個:
- Inject.ViewHost
- Inject.ViewControllerHost
我們?nèi)绾渭伤??我們把我們想迭代的類包裝在父級,因此我們不修改要注入的類型,而是改變父級的調(diào)用站點(diǎn)。
例如,如果你有一個 SplitViewController ,它創(chuàng)建了 PaneA 和 PaneB ,而你想在PaneA 中迭代布局/邏輯代碼,你就修改 SplitViewController 中的調(diào)用站點(diǎn)。
paneA = Inject.ViewHost(
PaneAView(whatever: arguments, you: want)
)
這就是你需要做的所有改變。注入現(xiàn)在允許你更改 PaneAView 中的任何東西,除了它的初始化API。這些變化將立即反映在你的應(yīng)用程序中。
一個更具體的例子?
- 我下載了 Covid19 App
- 添加 -Xlinker -interposable 到 Other Linker Flags
- 交換了一行 Covid19TabController.swift:L63 行
從這句:
let vc = TwitterViewController(title: Tab.twitter.name, usernames: Twitter.content)
替換為:
let vc = Inject.ViewControllerHost(TwitterViewController(title: Tab.twitter.name, usernames: Twitter.content))
現(xiàn)在,我可以在不重新啟動應(yīng)用程序的情況下迭代控制器設(shè)計。
這是如何運(yùn)作的呢?
Hosts 利用了自動閉包,因此每次您注入代碼時,我們都會使用與最初相同的參數(shù)創(chuàng)建您類型的新實(shí)例,從而允許您迭代任何代碼、內(nèi)存布局和其他所有內(nèi)容。你唯一不能改變的是你的初始化 API。
Host 的變化不能完全內(nèi)聯(lián),所以這些類在 Release 構(gòu)建中被刪除。最簡單的方法是做一個單獨(dú)的提交,交換此單行代碼,然后在工作流程的最后刪除它。
邏輯注入如何呢?
像 MVVM / MVC 這樣的標(biāo)準(zhǔn)架構(gòu)可以獲得免費(fèi)的邏輯注入,重新編譯你的類,當(dāng)方法重新執(zhí)行時,你已經(jīng)在使用新代碼了。
如果像我一樣,你喜歡 PointFree Composable Architecture[5],你可能想要注入 reducer 代碼。Vanilla TCA 不允許這樣做,因?yàn)?reducer 代碼是一個免費(fèi)功能,不能直接用注入替換,但我們在 The Browser Company 的分支 支持它。
當(dāng)我最初開始咨詢 TBC 時,我想要的第一件事是將 Inject? 和 XcodeInjection 集成到我們的工作流程中。公司管理層非常支持。
如果您切換到我們的 TCA 分支(我們保持最新),你可以在 UI 和 TCA 層上使用 Inject 。
它有多可靠?
沒有什么是完美的,但我已經(jīng)使用它十多年了。它比 Apple 技術(shù)(Playgrounds / Previews)可靠得多。
如果您投入時間學(xué)習(xí)它,它將為您和您的團(tuán)隊(duì)節(jié)省數(shù)千小時!
參考資料
[1] DyCI: https://github.com/DyCI/dyci-main
[2] Swift Secrets: http://books.apple.com/us/book/id1551005489
[3] GitHub repo: https://github.com/krzysztofzablocki/Inject
[4] Sourcery Pro: http://merowing.info/sourcery-pro/
[5] PointFree Composable Architecture: https://github.com/pointfreeco/swift-composable-architecture
[6] Demo 源碼: https://github.com/krzysztofzablocki/Inject