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