SwiftData查询Predicate表达式编译超时/失败的优化方案咨询
SwiftData多维度过滤编译超时/失败的解决方案
问题背景
在SwiftUI中使用SwiftData展示大量数据时,数据展示正常,但过滤逻辑存在编译问题:
- 简化版过滤代码编译耗时21秒
- 包含搜索文本、店铺、分类、子分类的多维度过滤代码直接编译失败,报错:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
简化版TestView代码
struct TestView: View { @Environment(\.modelContext) var modelContext @Query(sort: \GroceryItem.name) var groceryItems: [GroceryItem] init(sort: SortDescriptor<GroceryItem>, searchText: String, stores: Set<String>) { _groceryItems = Query(filter: #Predicate { // 这段表达式编译耗时较长 if searchText.isEmpty { if stores.isEmpty { return true } else { return stores.contains($0.store) } } else { if stores.isEmpty { return $0.name.localizedStandardContains(searchText) } else { return stores.contains($0.store) && $0.name.localizedStandardContains(searchText) } } } , sort: [sort]) } var body: some View { List(groceryItems) { item in GroceryItemCell(groceryItem: item, isAddGroceryItemView: true) { _ in print("Update total price") } .listRowInsets(EdgeInsets()) } } }
调用方式
TestView(sort: sortOrder, searchText: searchText, stores: selectedStores)
GroceryItem模型
@Model class GroceryItem: Codable, Identifiable { var id : String? var store : String var category : String? var subCategory : String? var name : String var itemDescription : [DescriptionItem]? var price : String? var pricePerType : String? var image : String? var url : String? var isItemSelected : Bool? var quantity = 1 }
无法编译的多维度过滤代码
init(sort: SortDescriptor<GroceryItem>, searchText: String, stores: Set<String>, categories: Set<String>, subCategories: Set<String>) { _groceryItems = Query(filter: #Predicate { if searchText.isEmpty { if stores.isEmpty { if categories.isEmpty { if subCategories.isEmpty { return true } else { return subCategories.contains($0.subCategory!) } } else { if subCategories.isEmpty { return categories.contains($0.category!) } else { return categories.contains($0.category!) && subCategories.contains($0.subCategory!) } } } else { if categories.isEmpty { if subCategories.isEmpty { return true } else { return subCategories.contains($0.subCategory!) } } else { if subCategories.isEmpty { return categories.contains($0.category!) } else { return categories.contains($0.category!) && subCategories.contains($0.subCategory!) } } } } else { if stores.isEmpty { if categories.isEmpty { if subCategories.isEmpty { return $0.name.localizedStandardContains(searchText) } else { return $0.name.localizedStandardContains(searchText) && subCategories.contains($0.subCategory!) } } else { if subCategories.isEmpty { return $0.name.localizedStandardContains(searchText) && categories.contains($0.category!) } else { return $0.name.localizedStandardContains(searchText) && categories.contains($0.category!) && subCategories.contains($0.subCategory!) } } } else { if categories.isEmpty { if subCategories.isEmpty { return $0.name.localizedStandardContains(searchText) && stores.contains($0.store) } else { return $0.name.localizedStandardContains(searchText) && stores.contains($0.store) && subCategories.contains($0.subCategory!) } } else { if subCategories.isEmpty { return $0.name.localizedStandardContains(searchText) && stores.contains($0.store) && categories.contains($0.category!) } else { return $0.name.localizedStandardContains(searchText) && stores.contains($0.store) && categories.contains($0.category!) && subCategories.contains($0.subCategory!) } } } } }, sort: [sort]) }
解决方案
核心思路是把复杂的嵌套条件拆分成多个独立的#Predicate片段,再通过逻辑运算符组合,降低编译器的类型检查复杂度。同时优化可选值处理,避免强制解包。
步骤1:拆分各维度的过滤谓词
为每个过滤维度单独定义谓词,只有当对应的过滤条件非空时,才应用该谓词:
// 搜索文本过滤 let searchPredicate = #Predicate<GroceryItem> { item in searchText.isEmpty || item.name.localizedStandardContains(searchText) } // 店铺过滤 let storePredicate = #Predicate<GroceryItem> { item in stores.isEmpty || stores.contains(item.store) } // 分类过滤(处理可选值,避免强制解包) let categoryPredicate = #Predicate<GroceryItem> { item in categories.isEmpty || (item.category != nil && categories.contains(item.category!)) } // 子分类过滤(处理可选值) let subCategoryPredicate = #Predicate<GroceryItem> { item in subCategories.isEmpty || (item.subCategory != nil && subCategories.contains(item.subCategory!)) }
步骤2:组合所有谓词
将上述独立谓词通过&&组合,形成最终的过滤条件:
let combinedPredicate = searchPredicate && storePredicate && categoryPredicate && subCategoryPredicate
步骤3:重构后的完整Init方法
init(sort: SortDescriptor<GroceryItem>, searchText: String, stores: Set<String>, categories: Set<String>, subCategories: Set<String>) { // 拆分各维度谓词 let searchPredicate = #Predicate<GroceryItem> { item in searchText.isEmpty || item.name.localizedStandardContains(searchText) } let storePredicate = #Predicate<GroceryItem> { item in stores.isEmpty || stores.contains(item.store) } let categoryPredicate = #Predicate<GroceryItem> { item in categories.isEmpty || (item.category != nil && categories.contains(item.category!)) } let subCategoryPredicate = #Predicate<GroceryItem> { item in subCategories.isEmpty || (item.subCategory != nil && subCategories.contains(item.subCategory!)) } // 组合谓词 let combinedPredicate = searchPredicate && storePredicate && categoryPredicate && subCategoryPredicate _groceryItems = Query(filter: combinedPredicate, sort: [sort]) }
额外优化建议
- 避免强制解包:如果
category或subCategory可能为空,建议在谓词中先判断非空,再进行包含检查,避免运行时崩溃。 - 提取复用逻辑:如果多个视图需要相同的过滤逻辑,可以把谓词组合逻辑封装成工具函数,比如
GroceryItemPredicates类,提升代码复用性。 - 编译性能验证:拆分后的代码编译器可以快速处理每个独立谓词,再组合,彻底解决编译超时或失败问题。
内容的提问来源于stack exchange,提问作者Abdullah Ajmal
相关产品推荐
相关产品推荐

