上一篇講到了組件及組件化,從概念和優/缺點兩個方向說明了組件化的意義,更為重要的是,組件和組件化是一個在編程領域,放之四海皆可以的概念,理解和運用它是非常必要的,希望大家能掌握。今天我們介紹另一個特性--預覽(Preview).
概念
預覽是蘋果給SwiftUI新添加的一個重要特性,也可以算得上是一個重大突破。它可以直接在macOS上進行渲染并顯示界面(即所見即所得),很類似Flutter的Hot Reloading。熟悉Hot Reloading技術的同學都知道,它給我們編程帶來很多的方便。
然而,相比Hot Reloading技術,預覽又有些不同,甚至可以說更好一些。因為Hot Reloading技術需要運行App,并且當調整界面或數據狀態發生變化的時,需要重啟App。而預覽就完全不需要做這些事情(不用運行App;數據變化也不需要重啟App)。
再說明一點,預覽的實現機制整體概括為:Xcode工具對代碼進行靜態分析(依賴于SwiftSyntax框架),然后找到所有遵循ProviewProvider協議的類型,進而對此進行渲染和顯示。
講完了概念,用示例圖來展示預覽情況。
現象
上圖表示:
1.左邊是編寫代碼區域,右邊是實時預覽區域,實現了所見即所得的效果。
2.紅框標注了遵循ProviewProvider協議。
介紹完了預覽的概念和樣式。大家一定很好奇預覽功能是怎么實現的?它的內部機制是怎么樣的呢?下面我們一起看下。
內部機制
我們先拿個工程來舉例。先創建一個SwiftUI的新工程(注意:先不要寫任何代碼,也不啟動預覽功能)。如下圖所示
然后找到DerivedData目錄。找一個后綴.preview-thunk.swift的文件
此時,在這個目錄上是找不到的(因為沒有編譯,連工程目錄都沒有)。
接著編譯(Command+B)工程(記得開啟預覽功能).就可以在DerivedData目錄上找到 .preview-thunk.swift文件了。對于本機來說,.preview-thunk.swift的完整路徑為:
DerivedData/工程目錄/Build/Intermediates.noindex/Previews/TestSwiftUIDemo5/Intermediates.noindex/TestSwiftUIDemo5.build/Debug-iphonesimulator/TestSwiftUIDemo5.build/Objects-normal/x86_64/ContentView.5.preview-thunk.swift.
文件效果如下圖所示
然后打開該文件。會顯示如下代碼
@_private(sourceFile: "ContentView.swift") import TestSwiftUIDemo5
import SwiftUI
import SwiftUIextension ContentView_Previews {@_dynamicReplacement(for: previews) private static var __preview__previews: some View {#sourceLocation(file: "/Users/hyh/Documents/MyProject/TestSwiftUIDemo5/TestSwiftUIDemo5/ContentView.swift", line: 19)__designTimeSelection(ContentView(), "#5016.[2].[0].property.[0].[0]")#sourceLocation()}
}extension ContentView {@_dynamicReplacement(for: body) private var __preview__body: some View {#sourceLocation(file: "/Users/hyh/Documents/MyProject/TestSwiftUIDemo5/TestSwiftUIDemo5/ContentView.swift", line: 12)__designTimeSelection(Text(__designTimeString("#5016.[1].[0].property.[0].[0].arg[0].value", fallback: "Hello, world!")).padding(), "#5016.[1].[0].property.[0].[0]")#sourceLocation()}
}import struct TestSwiftUIDemo5.ContentView
import struct TestSwiftUIDemo5.ContentView_Previews
這個代碼不用看懂,我們只需要了解下面幾個特性:
-
@_private(sourceFile: ): 讓當前代碼可以訪問原本外部無法訪問的變量和函數,這樣我們就無需在項目代碼中提高訪問權限。簡單來說,通過該函數能直接訪問一些不能訪問的變量或函數。
-
#sourceLocation(file: ,line: ):負責將衍生代碼中發生的崩潰等調試信息反映在我們寫的代碼上,幫助開發者找到對應的源代碼位置。等同于錯誤日志輸入。
-
@_dynamicReplacement(for: ):指定某個方法作為另一個方法的動態替代方法。在上面的代碼中,主要是__preview__previews 函數并讓它作為預覽入口。
-
最后兩行import代碼,是導入struct TestSwiftUIDemo5.ContentView和
struct TestSwiftUIDemo5.ContentView_Previews 兩個結構體的相關信息(包含變量),保證代碼的編譯成功。
以上代碼,簡單來說,就是確定一個入口函數,并且依賴上自己編寫的代碼的相關信息。
這里注意下:
在該目錄下有兩個.preview-thunk.swift文件,經過實踐驗證:這兩個文件內容基本一樣,一起變化(代碼一變化,兩文件內容一起變化),所以存在兩個一樣的文件。
然而,Xcode如何加載預覽視圖的呢?
這個就需要查看ContentView.5.preview-thunk.dylib(.dylib后綴是動態庫)。如下圖所示
然后打開.dylib文件,需要在在當前目錄下,在終端執行
nm ./ContentView.5.preview-thunk.dylib | grep ' T '//該命令作用是羅列出.dylib文件中的符號
執行結果如下圖
其實,該動態庫只有一個_main 方法。在該方法中,主要進行了定義預覽相關的環境設置、設置預覽初始狀態等操作。然后,再創建了用于預覽的進程。并通過 XPC 在預覽進程與 Xcode 之間進行通信,最后實現了在 Xcode 中預覽特定視圖的目的。這就是Xcode加載預覽視圖的整個過程。
最后總結下預覽的工作流程。
預覽的工作流程(該流程描述來自網絡)
-
Xcode 生成預覽衍生代碼文件
-
Xcode 編譯整個項目,解析文件、獲取預覽視圖實現、準備依賴的其他資源
-
Xcode 編譯預覽衍生代碼文件,創建動態庫
-
Xcode 啟動預覽線程,在其中加載 _XCPreviewKit 框架和預覽衍生文件生成的 dylib
-
XCPreviewKit 框架在預覽線程中創建預覽窗口
-
Xcode 通過 XPC 發送消息指令, _XCPreviewKit 框架更新預覽窗口,并在兩個線程建進行交互與同步
-
用戶在 Xcode 界面中看到預覽效果
以上就是預覽的概念和內部機制的介紹。
參考
https://zhuanlan.zhihu.com/p/631420119
https://www.guardsquare.com/blog/behind-swiftui-previews