簡體   English   中英

屬性包裝器和 SwiftUI 環境:屬性包裝器如何訪問其封閉 object 的環境?

[英]Property wrappers and SwiftUI environment: how can a property wrapper access the environment of its enclosing object?

SwiftUI 附帶的@FetchRequest屬性包裝器有助於聲明在 Core Data 存儲更改時自動更新的屬性。 您只需提供一個獲取請求:

struct MyView: View {
    @FetchRequest(fetchRequest: /* some fetch request */)
    var myValues: FetchedResults<MyValue>
}

如果沒有托管 object 上下文,提取請求無法訪問存儲。 此上下文必須在視圖的環境中傳遞。

現在我很困惑。

是否有任何公共 API 允許屬性包裝器訪問其封閉的 object 的環境,或者讓 SwiftUI 將此環境提供給屬性包裝器?

我們不知道如何實現SwiftUI的確切內部原理,但是我們可以根據可用的信息做出一些有根據的猜測。

首先, @propertyWrapper 不會從其包含的struct / class中自動訪問任何種類的上下文。 您可以查看規范以獲取證明。 在演化過程中對此進行了幾次討論,但未被接受。

因此,我們知道框架在運行時必須發生一些事情,才能將@EnvironmentObject (此處為NSManagedObjectContext )注入@FetchRequest 有關如何通過Mirror API執行類似操作的示例,您可以在此問題中看到我的答案 (順便說一句,它是在@Property可用之前編寫的,因此特定示例不再有用)。

但是, 本文建議使用@State的示例實現並推測(基於程序集轉儲),而不是使用Mirror API,SwiftUI使用TypeMetadata來提高速度:

無鏡反射

仍然有一種無需使用鏡像即可獲取字段的方法。 它使用元數據。

元數據具有字段描述符,其中包含該類型字段的訪問器。 可以通過使用它來獲取字段。

我的各種實驗結果AttributeGraph.framework在內部使用元數據。 AttributeGraph.framework是SwiftUI內部用於構造ViewGraph的私有框架。

您可以通過框架的符號來查看它。

$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph符號列表中有AG :: swift :: metadata_visitor :: visit_field。 我沒有分析整個匯編代碼,但名稱暗示AttributeGraph使用訪問者模式來解析元數據。

DynamicProperty結構可以簡單地聲明@Environment並且它將在調用update之前設置,例如

struct FetchRequest2: DynamicProperty {
    @Environment(\.managedObjectContext) private var context
    @StateObject private var controller = FetchController()

    func update(){
        // context will now be valid
        // set the context on the controller and do some fetching.
    }

和Xcode 13只要你的屬性包裝器械(尚未在早期版本的測試) DynamicProperty可以使用@Environment屬性包裝。

下面的示例創建一個屬性包裝器, lineSpacing從當前環境中讀取lineSpacing

@propertyWrapper
struct LineSpacing: DynamicProperty {
    @Environment(\.lineSpacing) var lineSpacing: CGFloat
    
    var wrappedValue: CGFloat {
        lineSpacing
    }
}

然后您可以像使用任何其他屬性包裝器一樣使用它:

struct LineSpacingDisplayView: View {
    @LineSpacing private var lineSpacing: CGFloat
    
    var body: some View {
        Text("Line spacing: \(lineSpacing)")
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            LineSpacingDisplayView()
            LineSpacingDisplayView()
                .environment(\.lineSpacing, 99)
        }
    }
}

這顯示:

行距:0.000000

行距:99.000000

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM