背景
这几天在使用swiftdata通过枚举属性过滤数据场景下,碰到一个很奇怪的问题,初看啥没啥问题,代码如下:
@Model
class Book: Equatable, Identifiable {
let id = UUID()
var bookType: BookType = BookType.STORY
var createTime: Date
init(bookType: BookType, createTime: Date) {
self.bookType = bookType
self.createTime = createTime
}
}
extension Book {
enum BookType: String, CaseIterable, Codable, Identifiable {
var id: Self {
return self;
}
case STORY = "story"
case SCIENCE = "science"
}
}
struct OneView: View {
@Query private books: [Book]
init(_ bookType: Book.BookType) {
let condition = type
let predicate = #Predicate<Book> {
$0.bookType == condition
}
self._books = Query(
filter: predicate,
sort: \Book.createTime,
order: .reverse,
animation: .default
)
}
}
结果出现一个错误:unsupportedPredicate
;而自然而然想要通过枚举的.rawValue
属性来去尝试:
struct OneView: View {
@Query private books: [Book]
init(_ bookType: Book.BookType) {
let condition = type.rawValue
let predicate = #Predicate<Book> {
$0.bookType.rawValue == condition
}
self._books = Query(
filter: predicate,
sort: \Book.createTime,
order: .reverse,
animation: .default
)
}
}
嗯,没有上面那个错误了,但是换了个错误:Can't find the property \Book.BookType.rawValue
查了下相关的资料,然后我找到了苹果开发者论坛里面一模一样的一个问题:
由于苹果并未开放SwiftUI的源代码,接下来将会结合一些资料以及宏展开等来探究这个问题的根因。如果有同学对这一块不感兴趣,我先直接列出对应的临时解决方案:
- 在model定义内新增加一个
_bookType
,然后增加bookType
非持久化字段,以利用_bookType
来获取和设置bookType
这个枚举值
@Model
class Book {
let id = UUID()
@Transient var bookType: BookType {
get { Book.BookType(rawValue: _bookType) }
set { _bookType = newValue.rawValue }
}
var _bookType: String
var createTime: Date
}
2. 在View页面使用_bookType字段来进行条件过滤:
struct OneView: View {
@Query private books: [Book]
init(_ bookType: Book.BookType) {
let condition = type.rawRype
let predicate = #Predicate<Book> {
$0._bookType == condition
}
self._books = Query(
filter: predicate,
sort: \Book.createTime,
order: .reverse,
animation: .default
)
}
}
通过上述的变更后,根据枚举属性过滤数据就没有问题了
原因
在有了上述这个解决方案后,我们也该探究一下为什么会出现这个问题。
由于SwiftUI是闭源代码,无法从源码层面直接分析其查询的逻辑,但是我们可以通过宏展开来做一定的判断。(PS:@Query无法做宏展开,因此,这里将会从#Predicate来间接判断其处理过程)
无法使用枚举等值比较的原因
let predicate = #Predicate<Book> {
$0.bookType == condition
}
上述代码报错Unsupported Predicate
,这个其实也没啥好分析的了,就是枚举比较在Predicate内无法被支持
无法使用枚举.rawValue
做比较的原因
let predicate = #Predicate<Book> {
$0.bookType.rawValue == condition
}
上述代码报错
原因分析
我们将#Predicate宏展开后的代码如下:
Foundation.Predicate<Book>({
PredicateExpressions.build_Equal(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: .bookType
),
keyPath: .rawValue
),
rhs: PredicateExpressions.build_Arg(condition)
)
})
可以看到,宏展开后,predicate表达式基本分为两个部分:lhs和rhs;即:左表达式和有表达式。
左边构建查询key路径,右边构建实参。
从左边构建的查询key路径也可看出,我们的$0.bookType.rawValue被分成了两个部分:一个是root,即:.bookType,另一个是keyPath,即:.rawValue。
而book的model是没有这个.rawValue的keyPath的,这个可以将book的@model做一个宏展开,里面的schemaMetadata属性上,会注册这个model所有的属性即路径:
static var schemaMetadata: [SwiftData.Schema.PropertyMetadata] {
return [
SwiftData.Schema.PropertyMetadata(name: "id", keypath: \Book.id, defaultValue: UUID(), metadata: nil),
SwiftData.Schema.PropertyMetadata(name: "bookType", keypath: \Book.bookType, defaultValue: BookType.STORY, metadata: nil),
SwiftData.Schema.PropertyMetadata(name: "createTime", keypath: \Book.createTime, defaultValue: nil, metadata: nil)
]
}
显而易见,这也是为什么会报错Can't find the property \Book.BookType.rawValue
的原因了。
小结
目前看来swiftui以及swiftdata在功能完备性上还是有一些小瑕疵的,后续有空的时候,将会进一步尝试下:更改schemaMetadata,注册rawKey到keypath是否可行。