前几天写了一篇关于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) -> 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 > 25") NSArray(array: [laogong, laoli, laozhang]).filtered(using: userPredicate) NSArray(array: [laogong, laoli, laozhang]).filtered(using: agePredicate) // Swift中谓词不能过滤这个数据类型,所以换种操作 [laogong, laoli, laozhang].filter { (userInfo) -> 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("[email protected]") |
例子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)”