CoreData请求(Fetch Request)中断言(NSPredicate)使用的一个误区

920 阅读2分钟

概述

CoreData中一个常见的操作就是获取数据(Fetching),NSFetchRequest自然是不二人选。

如果我们在请求数据时还要做一些过滤操作,那么断言(NSPredicate)此时就派上用处了。

不过,在创建CoreData请求使用的断言要小心,稍不留神就会出现“诡异”的查询结果。

下面,我们就来讨论一个使用断言的误区:貌似断言构造正确,但CoreData请求的结果总为空。

这是怎么回事呢?

让我们马上来看一下吧! ;)


"貌似正确"的代码

在如下代码中,我们根据不同条件,构造了一个供CoreData查询请求使用的断言:


struct FilteringTypes: OptionSet, Hashable {
    let rawValue: Int
    
    static let title = FilteringTypes(rawValue: 1 << 0)
    static let desc = FilteringTypes(rawValue: 1 << 1)
    static let url = FilteringTypes(rawValue: 1 << 2)
    
    static let titleAndDesc: FilteringTypes = [.title, .desc]
    static let all: FilteringTypes = [.title, .desc, .url]
}

let key = searchText
if !key.isEmpty {
    
    var formats = [String]()
    var args = [Any]()
    
    // types类型为FilteringTypes
    if types.contains(.title) {
        formats.append("title CONTAINS[cd] %@")
        args.append(key)
    }
    
    if types.contains(.desc) {
        formats.append("desc CONTAINS[cd] %@")
        args.append(key)
    }
    
    if types.contains(.url) {
        formats.append("url CONTAINS[cd] %@")
        args.append(key)
    }
    
    let pstr = formats.joined(separator: " OR ")
    // 创建断言
    let predicate = NSPredicate(format: pstr, args)
}

可以看到,我们根据types变量的值来生成对应的查询断言,上面代码可以正常编译,但是查询结果总为空。

挑错

为了定位问题所在,我们在 types的值为.title,key字符串为"sw" 时打印出断言的条件表达式:

title CONTAINS[cd] {"sw"}

感觉很奇怪的样子,为什么 "sw" 会被大括号包裹呢?

我们直接生成一个相同条件的断言来看看:

(lldb) po NSPredicate(format: "title CONTAINS[cd] %@", "sw")
title CONTAINS[cd] "sw"

果然,正常的断言中的字符串变量外围是没有大括号的。

所以说,这必然是之前构造断言的代码有问题,仔细看一下使用的构造器签名:

NSPredicate(format: pstr, args)

心细的小伙伴可能已经发现了,因为我们用[Any]参数数组创建断言,所以正确的构造方法应该是:

NSPredicate(format: pstr, argumentArray: args)

之前的构造器相当于把参数数组当做一个参数传递给断言表达式,这就是 "sw" 被大括号包围的原因:{"sw"} 代表一个数组啊!

尾声

在使用正确的断言构造器后,我们CoreData查询请求的结果都没问题了。

所以,小伙伴们在遇到此类问题一定要特别留心,往往很像的构造器,它们的差别可能会相差十万八千里。

感谢观赏,再会。8-)