iOS复习笔记——谓词(NSPredicate)

前几天写了一篇关于iOS中正则表达式的使用笔记,正则表达式在一堆不规则的文本中匹配出想要的内容时功能强大。但很多时候我们只需要获取一个bool值,比如判断是字符串是否是合法的email,是否是合法的手机号码,是否是合法的URL等,此时正则显得过于臃肿而且效率低下。这个时候谓词(NSPredicate)就能派上大用场了。

什么是谓词,以下是官方定义:

The NSPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering.
Predicates wrap some combination of expressions and operators and when evaluated return a BOOL.

NSPredicate类是用来定义逻辑条件约束的获取或内存中的过滤搜索。
谓词包含表达式和运算符的一些组合,鉴定后返回一个BOOL值。

其实简单来说,谓词就是一个过滤器,不符合该谓词过滤条件的统统返回false滚粗。

注意事项

1. 谓词基于在对对象的属性进行判断时基于KVC,所以不是继承于NSObject的一切对象都不能使用
2. OC中谓词可以对普通数据类型进行匹配,如NSInteger,但Swift中不行,例如在Swift中只能使用NSNumber
3. 谓词进行正则匹配时只能匹配结果唯一的正则,可能有多个结果返回的正则应避免使用

NSPredicate的基本语法

谓词表达式由3部分构成:表达式,运算符和值

1. 比较运算符和逻辑运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
>=,=>:判断左边表达式的值是否大于或等于右边表达式的值
<=,=<:判断右边表达式的值是否小于或等于右边表达式的值 >:判断左边表达式的值是否大于右边表达式的值
<:判断左边表达式的值是否小于右边表达式的值
!=、<>:判断两个表达式是否不相等
BETWEEN:BETWEEN表达式必须满足表达式 BETWEEN {下限,上限}的格式,要求该表达式必须大于或等于下限,并小于或等于上限
*/


// SELF = 123, 判断自身是否等于123,谓词没有赋值这一说,=, == 都是表示等于
// let numberPredicate = NSPredicate(format: "SELF = %d", 123)
// SELF != 123 (!=、<>)都表示不等于
// let numberPredicate = NSPredicate(format: "SELF <> %d", 123)
// SELF 大于等于 123
// let numberPredicate = NSPredicate(format: "SELF => %d", 123)

let numberPredicate = NSPredicate(format: "SELF BETWEEN {50, 200}")

numberPredicate.evaluate(with: testNumber)

let testArray: [NSNumber] = [NSNumber(value: 1),
NSNumber(value: 2),
NSNumber(value: 3),
NSNumber(value: 4),
NSNumber(value: 5)]
// AND、&&:逻辑与,要求两个表达式的值都为YES时,结果才为YES。
// NOT、 !:逻辑非,对原有的表达式取反
// OR、||:逻辑或,要求其中一个表达式为YES时,结果就是YES
// let arrayPredicate = NSPredicate(format: "SELF > 3 AND SELF != 5")
let arrayPredicate = NSPredicate(format: "SELF > 4 || SELF < 2")

let tempNSArray = NSArray(array: testArray).filtered(using: arrayPredicate)

2. 字符串比较运算符

BEGINSWITH:检查某个字符串是否以指定的字符串开头(如判断字符串是否以a开头:BEGINSWITH ‘a’)
ENDSWITH:检查某个字符串是否以指定的字符串结尾
CONTAINS:检查某个字符串是否包含指定的字符串
LIKE:检查某个字符串是否匹配指定的字符串模板。其之后可以跟?代表一个字符和*代表任意多个字符两个通配符。比如”name LIKE ‘*ac*'”,这表示name的值中包含ac则返回YES;”name LIKE ‘?am*'”,表示name的第2、3个字符为am时返回YES。跟正则表达式的占位一致。
注:字符串比较都是区分大小写和重音符号的。如:café和cafe是不一样的,Cafe和cafe也是不一样的。如果希望字符串比较运算不区分大小写和重音符号,请在这些运算符后使用[c],[d]选项。其中[c]是不区分大小写,[d]是不区分重音符号,其写在字符串比较运算符之后,比如:name LIKE[cd] ‘cafe’,那么不论name是cafe、Cafe还是café上面的表达式都会返回YES。
MATCHES:检查某个字符串是否匹配指定的正则表达式。虽然正则表达式的执行效率是最低的,但其功能是最强大的,也是我们最常用的。

3. 集合运算符

ANY、SOME:集合中任意一个元素满足条件,就返回YES。
ALL:集合中所有元素都满足条件,才返回YES。
NONE:集合中没有任何元素满足条件就返回YES。如:NONE user.age < 18,表示user集合中所有元素的age >= 18时,才返回YES。
IN:等价于SQL语句中的IN运算符,只有当左边表达式或值出现在右边的集合中才会返回YES。我们通过一个例子来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
let filterArray = NSArray(array: ["ab", "abc"])

let contentsArray = NSArray(array: ["a", "ab", "abc", "abcd"])

let wordsPredicate = NSPredicate(format: "NOT (SELF IN %@)", filterArray)

contentsArray.filtered(using: wordsPredicate)

// Swift中数组不能使用谓词进行筛选,但可以采用如下方式
["a", "ab", "abc", "abcd"].filter { (tempStr) -&gt; Bool in
return wordsPredicate.evaluate(with: tempStr)
// return !filterArray.contains(tempStr)
}

4. 直接量

在谓词表达式中可以使用如下直接量

  • FALSE、NO:代表逻辑假
  • TRUE、YES:代表逻辑真
  • NULL、NIL:代表空值
  • SELF:代表正在被判断的对象自身
  • “string”或’String’:代表字符串
  • 数组:和c中的写法相同,如:{‘one’, ‘two’, ‘three’}。
  • 数值:包括证书、小数和科学计数法表示的形式
  • 十六进制数:0x开头的数字
  • 八进制:0o开头的数字
  • 二进制:0b开头的数字

5.保留字

下列单词都是保留字(不论大小写)

AND、OR、IN、NOT、ALL、ANY、SOME、NONE、LIKE、CASEINSENSITIVE、CI、MATCHES、CONTAINS、BEGINSWITH、ENDSWITH、BETWEEN、NULL、NIL、SELF、TRUE、YES、FALSE、NO、FIRST、LAST、SIZE、ANYKEY、SUBQUERY、CAST、TRUEPREDICATE、FALSEPREDICATE

注:虽然大小写都可以,但是更推荐使用大写来表示这些保留字

谓词的用法

废话不来,直接上Code:

例子1, 普通使用和过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 这里如果是模型对象要使用谓词,则必须继承于NSObject,因为谓词取值比较的时候需要使用KVC
class LGUserInfo: NSObject {
override init() {
super.init()
}

public enum LGGender: Int {
case male = 0
case female
}

// 名字
public var realname: String?

// 年龄
public var age: NSNumber?
// 性别,Swift中谓词并不能过滤这种基础类型,但String,Date等源自OC的类型则可以
public var gender: LGGender = LGGender.male
}

var laogong = LGUserInfo()
laogong.age = 18
laogong.gender = LGUserInfo.LGGender.male
laogong.realname = "laogong"
laogong.setValue("laogong1", forKey: "realname")

let laozhang = LGUserInfo()
laozhang.age = 37
laozhang.gender = LGUserInfo.LGGender.female
laozhang.realname = "laozhang"

let laoli = LGUserInfo()
laoli.age = 55
laoli.gender = LGUserInfo.LGGender.female
laoli.realname = "laoli"
laoli.value(forKey: "age")

// 过滤以laog开头的字符
let userPredicate = NSPredicate(format: "realname LIKE 'laog*'")

userPredicate.evaluate(with: laogong)

// 年龄大于25的数据
let agePredicate = NSPredicate(format: "age &gt; 25")

NSArray(array: [laogong, laoli, laozhang]).filtered(using: userPredicate)
NSArray(array: [laogong, laoli, laozhang]).filtered(using: agePredicate)

// Swift中谓词不能过滤这个数据类型,所以换种操作
[laogong, laoli, laozhang].filter { (userInfo) -&gt; Bool in
return (userInfo.gender == .female)
}

例子2,正则判断Email:

1
2
3
4
5
6
7
8
func validateEmail(_ emailString : String) ->Bool
{
    let emailRegex = "^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$"
    let emailMatch = NSPredicate(format: "SELF MATCHES %@", emailRegex)
    return emailMatch.evaluate(with: emailString)
}

validateEmail("gjhlovelh@vip.qq.com")

例子3,正则表达式的缺陷:

谓词在匹配有多个结果的正则表达式时并不能正确返回,但例如上面例子2的email格式,指定开头和结尾,返回结果唯一的时候则可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 匹配字幕,数字和下划线
let pattern = "[A-Za-z0-9_]"

do {
    let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
    let results = regex.matches(in: "abcd哦豁cc", options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSMakeRange(0, 8))
    // 这里匹配出6个结果
    results.map {
        print((NSString(string: "abcd哦豁cc")).substring(with: $0.range))
    }
}
catch {
   
}

let regexPredicate = NSPredicate(format: "SELF MATCHES %@", pattern)
regexPredicate.evaluate(with: "a") // true
regexPredicate.evaluate(with: "abcd哦豁cc") // false

使用谓词过滤集合:

上面已经有多处使用到数组的过滤,那些即是集合过滤的实际用法,苹果提供了以下方法,可以看出对于可变集合,直接改变原有集合的值存储,非可变集合则返回一个新的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension NSArray {
    open func filtered(using predicate: NSPredicate) -> [Any]
}

extension NSMutableArray {
    open func filter(using predicate: NSPredicate)
}

extension NSSet {
    open func filtered(using predicate: NSPredicate) -> Set<AnyHashable>
}

extension NSMutableSet {
    open func filter(using predicate: NSPredicate)
}

extension NSOrderedSet {
    open func filtered(using p: NSPredicate) -> NSOrderedSet
}

extension NSMutableOrderedSet {
    open func filter(using p: NSPredicate)
}

在谓词中使用占位符参数

我们上面所有的例子中谓词总是固定的,然而我们在现实中处理变量时决定了谓词应该是可变的。下面我们来看看如果让谓词变化起来。

首先如果我们想在谓词表达式中使用变量,那么我们需要了解下列两种占位符:

%K:用于动态传入属性名
%@:用于动态设置属性值
其实相当于变量名与变量值

除此之外,还可以在谓词表达式中使用动态改变的属性值,就像环境变量一样

1
2
3
4
5
6
7
let dynamicPre = NSPredicate(format: "%K > $VALUE", "age")
var pred1 = dynamicPre.withSubstitutionVariables(["VALUE": 25])

NSArray(arrayLiteral: laogong, laoli, laozhang).filtered(using: pred1)

pred1 = dynamicPre.withSubstitutionVariables(["VALUE": 37])
NSArray(arrayLiteral: laogong, laoli, laozhang).filtered(using: pred1)

以上例子中均未列出具体的执行结果,如果有兴趣的可以自行下载本文playground,或自己参照例子进行实际编码验证

参考资料:http://www.cocoachina.com/ios/20160111/14926.html

One thought on “iOS复习笔记——谓词(NSPredicate)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注