简体   繁体   English

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

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

The @FetchRequest property wrapper that ships with SwiftUI helps declaring properties that are auto-updated whenever a Core Data storage changes. SwiftUI 附带的@FetchRequest属性包装器有助于声明在 Core Data 存储更改时自动更新的属性。 You only have to provide a fetch request:您只需提供一个获取请求:

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

The fetch request can't access the storage without a managed object context.如果没有托管 object 上下文,提取请求无法访问存储。 This context has to be passed in the view's environment.此上下文必须在视图的环境中传递。

And now I'm quite puzzled.现在我很困惑。

Is there any public API that allows a property wrapper to access the environment of its enclosing object, or to have SwiftUI give this environment to the property wrapper?是否有任何公共 API 允许属性包装器访问其封闭的 object 的环境,或者让 SwiftUI 将此环境提供给属性包装器?

We don't know the exact internals of how SwiftUI is implemented, but we can make some educated guesses based on the information we have available. 我们不知道如何实现SwiftUI的确切内部原理,但是我们可以根据可用的信息做出一些有根据的猜测。

First, @propertyWrapper s do not get automatic access to any kind of context from their containing struct/class. 首先, @propertyWrapper 不会从其包含的struct / class中自动访问任何种类的上下文。 You can check out the spec for evidence of that. 您可以查看规范以获取证明。 This was discussed a few times during the evolution process, but not accepted. 在演化过程中对此进行了几次讨论,但未被接受。

Therefore, we know that something has to happen at runtime for the framework to inject the @EnvironmentObject (here the NSManagedObjectContext ) into the @FetchRequest . 因此,我们知道框架在运行时必须发生一些事情,才能将@EnvironmentObject (此处为NSManagedObjectContext )注入@FetchRequest For an example of how to do something like that via the Mirror API, you can see my answer in this question . 有关如何通过Mirror API执行类似操作的示例,您可以在此问题中看到我的答案 (By the way, that was written before @Property was available, so the specific example is no longer useful). (顺便说一句,它是在@Property可用之前编写的,因此特定示例不再有用)。

However, this article suggests a sample implementation of @State and speculates (based on an assembly dump) that rather than using the Mirror API, SwiftUI is using TypeMetadata for speed: 但是, 本文建议使用@State的示例实现并推测(基于程序集转储),而不是使用Mirror API,SwiftUI使用TypeMetadata来提高速度:

Reflection without Mirror 无镜反射

There is still a way to get fields without using Mirror. 仍然有一种无需使用镜像即可获取字段的方法。 It's using metadata. 它使用元数据。

Metadata has Field Descriptor which contains accessors for fields of the type. 元数据具有字段描述符,其中包含该类型字段的访问器。 It's possible to get fields by using it. 可以通过使用它来获取字段。

My various experiments result AttributeGraph.framework uses metadata internally. 我的各种实验结果AttributeGraph.framework在内部使用元数据。 AttributeGraph.framework is a private framework that SwiftUI use internally for constructing ViewGraph. AttributeGraph.framework是SwiftUI内部用于构造ViewGraph的私有框架。

You can see it by the symbols of the framework. 您可以通过框架的符号来查看它。

$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph There is AG::swift::metadata_visitor::visit_field in the list of symbols. $ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph符号列表中有AG :: swift :: metadata_visitor :: visit_field。 i didn't analysis the whole of assembly code but the name implies that AttributeGraph use visitor pattern to parse metadata. 我没有分析整个汇编代码,但名称暗示AttributeGraph使用访问者模式来解析元数据。

A DynamicProperty struct can simply declare @Environment and it will be set before update is called eg 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.
    }

With Xcode 13 (haven't tested on earlier versions) as long as your property wrapper implements DynamicProperty you can use the @Environment property wrapper.和Xcode 13只要你的属性包装器械(尚未在早期版本的测试) DynamicProperty可以使用@Environment属性包装。

The following example create a property wrapper that's read the lineSpacing from the current environment.下面的示例创建一个属性包装器, lineSpacing从当前环境中读取lineSpacing

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

Then you can use it just like any other property wrapper:然后您可以像使用任何其他属性包装器一样使用它:

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)
        }
    }
}

This displays:这显示:

Line spacing: 0.000000行距:0.000000

Line spacing: 99.000000行距:99.000000

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM