繁体   English   中英

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

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

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