如何结合传入View的对象使用@FetchRequest的nsPredicate动态属性
为什么第一种attributes.nsPredicate = ...写法会崩溃?
@FetchRequest是SwiftUI的动态属性包装器,它的运行完全依赖SwiftUI注入的环境值(包括Core Data的管理上下文)。在View的init执行阶段,属性包装器的底层wrappedValue(也就是你直接访问的attributes变量,类型为FetchedResults<Attribute>)还未完成环境绑定,处于未完全初始化的状态,此时直接操作它的nsPredicate属性,会访问还没关联Core Data栈的无效上下文,直接触发崩溃。
为什么第二种写法能生效但有警告?
$attributes拿到的是@FetchRequest的投影值,你通过$attributes.projectedValue.wrappedValue访问的是属性包装器本身的配置属性,而非已经生成的FetchedResults实例。你修改的是请求的初始配置,等后续SwiftUI完成环境注入后,会用你修改后的nsPredicate执行查询,因此能拿到预期结果。
出现警告是因为修改配置时,属性包装器内部会尝试访问环境中的管理上下文,此时上下文还没被注入,也没有关联持久化存储协调器,因此触发运行时警告,这属于未定义行为的提示。
这个运行时警告需要处理吗?
必须处理。当前能正常运行只是巧合,属于SwiftUI当前实现的漏判,一旦后续SwiftUI版本调整了FetchRequest的内部逻辑,或者你修改的属性需要直接访问上下文,就会直接触发崩溃,这类依赖未定义行为的写法不应该在生产环境使用。
更优的实现方案
目前有两种经过验证的稳定写法可以覆盖不同场景:
- 方案一:init阶段直接构造正确的FetchRequest(最适配你当前的场景,predicate在init时就已确定)
struct ItemEditView : View { var item: Item @FetchRequest var attributes: FetchedResults<Attribute> init(item: Item) { self.item = item // 直接通过下划线访问属性包装器本身,初始化时就传入带predicate的请求 let fetchRequest = Attribute.fetchRequestAllInOrder() fetchRequest.predicate = NSPredicate(format: "item == %@", item) _attributes = FetchRequest(fetchRequest: fetchRequest, animation: .default) } var body: some View { List { ForEach(attributes) { attribute in Text("Name:\(attribute.name) Order:\(attribute.order)") } } } }
这种写法完全没有警告,也不会崩溃,每次视图初始化都会生成正确的请求,不存在predicate丢失的问题。
- 方案二:在视图生命周期回调中修改(适合predicate会动态变化的场景)
如果你的item可能在视图展示后发生变化,可以保留原有@FetchRequest的初始化写法,在onAppear和onChange中修改predicate,此时视图已经完成环境注入,操作不会有问题:
struct ItemEditView : View { var item: Item @FetchRequest(fetchRequest: Attribute.fetchRequestAllInOrder(), animation: .default) var attributes: FetchedResults<Attribute> var body: some View { List { ForEach(attributes) { attribute in Text("Name:\(attribute.name) Order:\(attribute.order)") } } .onAppear { attributes.nsPredicate = NSPredicate(format: "item == %@", item) } .onChange(of: item) { newItem in attributes.nsPredicate = NSPredicate(format: "item == %@", newItem) } } }
内容的提问来源于stack exchange,提问作者Chuck H

