属性包装器和SwiftUI环境:属性包装器如何访问其封闭对象的环境?

6

@FetchRequest是SwiftUI自带的属性包装器,它可以帮助声明属性,并在Core Data存储更改时自动更新。您只需提供一个获取请求:

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

没有托管对象上下文,fetch请求无法访问存储。此上下文必须在视图的环境中传递。

现在我很困惑。

是否有任何公共API允许属性包装器访问其封闭对象的环境,或者让SwiftUI将此环境提供给属性包装器?

3个回答

5

我们不知道SwiftUI的内部实现细节,但根据我们可用的信息,我们可以做出一些合理的猜测。

首先,@propertyWrapper无法自动访问其所在的结构体/类中的任何上下文。你可以查看规范来证明这一点。虽然在演进过程中多次讨论了这一点,但并未被接受。

因此,我们知道框架必须在运行时执行某些操作,将@EnvironmentObject(这里是NSManagedObjectContext)注入到@FetchRequest中。如果想了解如何使用Mirror API执行此类操作的示例,可以查看我在这个问题中的回答。(顺便说一句,这是在@Property可用之前编写的,因此具体示例已不再有用)。

然而,这篇文章提供了一个@State的示例实现,并猜测(基于汇编转储)SwiftUI可能使用TypeMetadata提高速度:

Reflection without Mirror

有一种方法可以在不使用Mirror的情况下获取字段,那就是使用元数据。

Metadata有Field Descriptor,其中包含类型字段的访问器。可以通过使用它来获取字段。

我的各种实验结果表明,AttributeGraph.framework在内部使用metadata。AttributeGraph.framework是SwiftUI在构建ViewGraph时内部使用的私有框架。

你可以通过查看该框架的符号来了解它。

$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph 在符号列表中有AG::swift::metadata_visitor::visit_field。我没有分析整个汇编代码,但这个名称意味着AttributeGraph使用访问者模式来解析元数据。


优秀的写作,表达得非常清晰,并且充满了实际有用的信息! - Gwendal Roué
另一个有趣的链接:https://github.com/Swinject/Swinject/issues/419 - Gwendal Roué

4

使用 Xcode 13(未在早期版本上测试),只要您的属性包装器实现了 DynamicProperty,您就可以使用 @Environment 属性包装器。

以下示例创建了一个属性包装器,从当前环境中读取 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


1
感谢您更新答案,@rraphael :-) DynamicProperty 确实是 @Query 属性包装器(类似于 GRDB 的 @FetchRequest)的实现方式:https://github.com/groue/GRDBQuery - Gwendal Roué

1
一种名为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.
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接