这是此处所涵盖的问题和答案的附加场景。
示例代码可在此处获得。
原始问题与此谓词相关:
let aPred = #Predicate<Event> {
$0.dateStart != nil &&
($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}
这给出了编译错误:
编译器无法以合理的方式对该表达式进行类型检查 时间;尝试将表达式分解为不同的子表达式。
可以借助此扩展来解决这个问题:(信用Mojtaba Hosseini)
extension Optional: Comparable where Wrapped: Comparable {
public static func < (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
guard let lhs, let rhs else { return false }
return lhs < rhs
}
}
现在的问题是,如果我向我的
@Model final class TestEvent
添加一个可选的 bool var isActive: Bool?
,我会再次遇到相同的错误。
let aPred = #Predicate<TestEvent> {
$0.isActive == true && //comment this out and it will compile
$0.dateStart != nil &&
($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}
条件:
理想的解决方案是将谓词分解为更小的谓词,并以类似的方式将它们组合起来,就像
CoreData using NSCompoundPredicate
中可能的那样
如果你能够展开宏,你就能明白为什么编译器不能按时进行类型检查了!这就是宏为第 4 行实现的内容:
Foundation.Predicate<TodoModel>({
PredicateExpressions.build_Disjunction(
lhs: PredicateExpressions.build_Disjunction(
lhs: PredicateExpressions.build_Conjunction(
lhs: PredicateExpressions.build_NotEqual(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateStart
),
rhs: PredicateExpressions.build_NilLiteral()
),
rhs: PredicateExpressions.build_Conjunction(
lhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateStart
),
rhs: PredicateExpressions.build_Arg(startOfDay),
op: .greaterThanOrEqual
),
rhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateStart
),
rhs: PredicateExpressions.build_Arg(endOfDay),
op: .lessThanOrEqual
)
)
),
rhs: PredicateExpressions.build_Conjunction(
lhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateEnd
),
rhs: PredicateExpressions.build_Arg(startOfDay),
op: .greaterThanOrEqual
),
rhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateEnd
),
rhs: PredicateExpressions.build_Arg(endOfDay),
op: .lessThanOrEqual
)
)
),
rhs: PredicateExpressions.build_Conjunction(
lhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateStart
),
rhs: PredicateExpressions.build_Arg(startOfDay),
op: .lessThan
),
rhs: PredicateExpressions.build_Comparison(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.dateEnd
),
rhs: PredicateExpressions.build_Arg(startOfDay),
op: .greaterThanOrEqual
)
)
)
})
虽然它是如此令人惊叹和强大,但不幸的是这种力量让苹果工程师在制作中带来了这样的混乱:)
因此,基于这些 exteam 实现,您可以实现类似于
build_Conjunction
和 build_Disjunction
的功能来合并 SwiftData 谓词:
(👇您只需要这个文件即可启用👇)
@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
/// Allows you to use an existing Predicate as a ``StandardPredicateExpression``
struct VariableWrappingExpression<T>: StandardPredicateExpression {
let predicate: Predicate<T>
let variable: PredicateExpressions.Variable<T>
func evaluate(_ bindings: PredicateBindings) throws -> Bool {
// resolve the variable
let value = try variable.evaluate(bindings)
// create bindings for the expression of the predicate
let innerBindings = bindings.binding(predicate.variable, to: value)
return try predicate.expression.evaluate(innerBindings)
}
}
@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
extension Predicate {
typealias Expression = any StandardPredicateExpression<Bool>
/// Returns the result of combining the predicates using the given closure.
///
/// - Parameters:
/// - predicates: an array of predicates to combine
/// - nextPartialResult: A closure that combines an accumulating expression and
/// an expression of the sequence into a new accumulating value, to be used
/// in the next call of the `nextPartialResult` closure or returned to
/// the caller.
/// - Returns: The final accumulated expression. If the sequence has no elements,
/// the result is `initialResult`.
static func combining<T>(
_ predicates: [Predicate<T>],
nextPartialResult: (Expression, Expression) -> Expression
) -> Predicate<T> {
return Predicate<T>({ variable in
let expressions = predicates.map({
VariableWrappingExpression<T>(predicate: $0, variable: variable)
})
guard let first = expressions.first else {
return PredicateExpressions.Value(true)
}
let closure: (any StandardPredicateExpression<Bool>, any StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> = {
nextPartialResult($0,$1)
}
return expressions.dropFirst().reduce(first, closure)
})
}
}
@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
public extension Array {
/// Joins multiple predicates with an ``PredicateExpressions.Conjunction``
/// - Returns: A predicate evaluating to true if **all** sub-predicates evaluate to true
func conjunction<T>() -> Predicate<T> where Element == Predicate<T> {
func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
PredicateExpressions.Conjunction(lhs: lhs, rhs: rhs)
}
return Predicate<T>.combining(self, nextPartialResult: {
buildConjunction(lhs: $0, rhs: $1)
})
}
/// Joins multiple predicates with an ``PredicateExpressions.Disjunction``
/// - Returns: A predicate evaluating to true if **any** sub-predicate evaluates to true
func disjunction<T>() -> Predicate<T> where Element == Predicate<T> {
func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
PredicateExpressions.Disjunction(lhs: lhs, rhs: rhs)
}
return Predicate<T>.combining(self, nextPartialResult: {
buildConjunction(lhs: $0, rhs: $1)
})
}
}
您可以将谓词分解为更小的块,例如:
let activePredicate = #Predicate<TodoModel> {
$0.isActive == true
}
let nullCheckPredicate = #Predicate<TodoModel> {
$0.dateStart != nil && $0.dateEnd != nil
}
let rangesPredicates = #Predicate<TodoModel> {
($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}
和
conjunct
和disjunct
他们喜欢:
[activePredicate, nullCheckPredicate, rangesPredicates].conjunction()
[activePredicate, nullCheckPredicate, rangesPredicates].disjunction()
如上所述,要点文件可以在here找到,并且学分转到 NoahKamara、nOk 和其他开发这些强大扩展的贡献者