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

Gwe*_*oué 6 swift swiftui

SwiftUI 附带的@FetchRequest属性包装器有助于声明每当核心数据存储发生更改时自动更新的属性。您只需提供一个获取请求:

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

如果没有托管对象上下文,获取请求将无法访问存储。该上下文必须在视图的环境中传递。

现在我很困惑。

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

ars*_*ius 5

我们不知道 SwiftUI 的具体实现方式,但我们可以根据现有信息做出一些有根据的猜测。

首先,@propertyWrappers不会从其包含的结构/类中自动访问任何类型的上下文。您可以查看规范以获取证据。这在演化过程中曾被讨论过几次,但没有被接受。

因此,我们知道框架在运行时必须发生一些事情才能将@EnvironmentObject(此处为NSManagedObjectContext)注入到@FetchRequest. 有关如何通过 API 执行类似操作的示例Mirror,您可以在这个问题中查看我的答案。(顺便说一下,之前已经@Property写过,所以具体的例子不再有用)。

然而,本文建议了一个示例实现@State,并推测(基于程序集转储)SwiftUI 不使用 Mirror API,而是使用TypeMetadata来提高速度:

没有镜子的反射

还有一种方法可以不使用 Mirror 来获取字段。它使用元数据。

元数据具有字段描述符,其中包含该类型字段的访问器。使用它可以获取字段。

我的各种实验结果 AttributeGraph.framework 在内部使用元数据。AttributeGraph.framework 是 SwiftUI 内部用于构建 ViewGraph 的私有框架。

您可以通过框架的符号看到它。

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


rra*_*ael 5

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

lineSpacing以下示例创建一个从当前环境读取的属性包装器。

@propertyWrapper
struct LineSpacing: DynamicProperty {
    @Environment(\.lineSpacing) var lineSpacing: CGFloat
    
    var wrappedValue: CGFloat {
        lineSpacing
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以像任何其他属性包装器一样使用它:

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这显示:

行距:0.000000

行距:99.000000