[英]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.