简要理解 - NSPredicate

简介

NSPredicate是基础库中用来过滤获取数据的类,类似于SQL中的where语句,但它提供了更为自然且高级的语言,在数据集合的查询上去定义了逻辑条件语句。

直接展示NSPredicate的用法可能比抽象的讲解更容易理解,但在这之前,我们先来学习其基础的语法。

基础语法

  • Parser Basics(谓语字符串的基本解析)

    谓词格式字符串对关键字、括号敏感,对空格不明感,且不会进行语义类型检测。

    $ :以$符号开头声明变量,例如 $VARIABLE_NAME

    %K :属性名

    %@ :属性值

    使用%@占位符只能表示一些表达式,而不能去表示一个谓词,否则会造成程序异常。

  • Basic Comparisons(基本比较运算)

    =、== : 判断左边表达式是否等于右边表达式。

    >=、 => : 判断左边表达式是否大于或等于右边表达式。

    <=、=< : 判断左边表达式是否小于或等于右边表达式。

    > : 判断左边表达式是否大于右边表达式。

    < : 判断左边表达式是否小于右边表达式。

    !=、<> : 判断左边表达式是否不等于右边表达式。

    BETWEEN : 判断值是否在区间内,形如 $INPUT BETWEEN { $LOWER, $UPPER }

  • Boolean Value Predicates(Boolen谓语)

    TRUEPREDICATE : 始终返回为真的谓词。

    FALSEPREDICATE : 始终返回为假的谓词。

  • Basic Compound Predicates(基本复合谓语)

    AND、&& : 逻辑与。

    OR、|| : 逻辑或。

    NOT、! : 逻辑非。

  • String Comparisons(字符串比较)

    字符串默认对字母大小写和发音敏感,但我们可以手动对其进行关闭。例如 firstName BEGINSWITH[cd] $FIRST_NAME。c、d分别对应关闭对字母大小写与发音的敏感。

    BEGINSWITH : 判断左边字符串内容是否以右边字符串开始。

    CONTAINS : 判断字符串内容是否包含右边字符串。

    ENDSWITH : 判断字符串内容是否以右边字符串结束。

    LIKE : 判断左边字符串是否等于右边字符串。* 通常在这里被用作通配符。

    : 表示匹配一个字符。

    * : 表示匹配0个或多个字符。

    注意:与通配符 ?* 的组合必须是以字符串的形式表示。

    MATCHES : 通过正则表达式判断左右两边表达式是否相等。

    UTI-CONFORMS-TOUTI-EQUALS : 这两个属于macOS开发部分,这里不讨论。

  • Aggregate Operations(集合操作)

    ANY、SOME : 指定满足后面表达式的一些/部分元素。例如ANY children.age < 18

    ALL : 指定满足后面表达式的所有元素。例如ALL children.age < 18

    NONE : 指定不满足后面表达式的元素。例如NONE children.age < 18。等同于NOT (ANY ...)

    IN : 等同于SQL的IN操作。判断左边元素集合是否在右边元素集合出现。例如name IN { 'Ben', 'Melissa', 'Nick' }。这些集合可以是array、set、或者是dictionary。如果是dictionary,将取其values值。

    array[index] : 指定数组array中指定索引代表的元素。

    array[FIRST] : 指定数组array的第一个元素。

    array[LAST] : 指定数组array的最后一个元素。

    array[SIZE] : 指定数组array的大小。

  • Identifiers(标识符)

    所有C风格的标识符都不被保留。

    #symbol : 将保留字转义为用户标识符。

    [\]{octaldigit}{3} : 转义8进制数字。(\后跟上3个八进制数字)

    [\][xX]{hexdigit}{2} : 转义16进制数字。(\x/\X后跟上2十六进制数字)

    [\][uU]{hexdigit}{4} : 转义Unicode。(\u/\U后跟上4个十六进制数字)

  • Literals(字面量语义)

    单引号与双引号有着同样的效果,但是他们不能使对方结束。例如 "abc" 与 `abc` 是等价的,但是 "a`b`c" 却等同于 a + space + 'b' + space + c.

    FALSE、NO : 逻辑假。

    TRUE、YES : 逻辑真。

    NULL、NIL : 表示null值。

    SELF : 表示被操作的对象。

    "text" : 表示一个字符串。

    `text` : 表示一个字符串。

  • Reserved Words(保留字)

    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, FETCH, CAST, TRUEPREDICATE, FALSEPREDICATE, UTI-CONFORMS-TO, UTI-EQUALS

基础用法

Creating Predicate(创建谓词)

  1. Creating a Predicate Using a Format String

    通常我们使用NSPredicate的类方法+ (NSPredicate *)predicateWithFormat:(NSString *)predicateFormat, ...;来定义一个谓词,因为编译器是不会对字符串进行语义类型的检测,导致错误在编译时无法被察觉,所以我们必须严格进行编写,否则在替换变量的一些情况下会造成运行时错误。

  • String Constants, Variables, and Wildcards(字符串常量/变量,通配符)

    在谓词格式字符串中插入字符串常量,需要用到成对的单引号或者双引号且使用转义字符\,例如NSPredicate *predicate = [NSPredicate predicateWithFormat:@"lastName like[c] \"S*\""];

    在谓词格式字符串中插入占位符%@,那么%@表示的内容会被自动的添加上引号。这里要特别注意的是%@不能与通配符*或者?直接使用,而必须使用拼接的方式。例如

    NSString *prefix = @"prefix";
    NSString *suffix = @"suffix";
    NSPredicate *predicate = [NSPredicate
    predicateWithFormat:@"SELF like[c] %@",
    [[prefix stringByAppendingString:@"*"] stringByAppendingString:suffix]];
    BOOL ok = [predicate evaluateWithObject:@"prefixxxxxxsuffix"];
    复制代码

    在谓词格式字符串中插入变量,例如

    predicate = [NSPredicate
    predicateWithFormat:@"lastName like[c] $LAST_NAME"];
    复制代码
  • Boolean Values(Boolean值)

    指定和测试Boolean值的相等性。例如

    NSPredicate *newPredicate =
    [NSPredicate predicateWithFormat:@"anAttribute == %@", [NSNumber numberWithBool:aBool]];
    NSPredicate *testForTrue =
    [NSPredicate predicateWithFormat:@"anAttribute == YES"];
    复制代码
  • Dynamic Property Names(动态属性名)

    我们不能使用%@表示属性名,因为使用%@占位符表示后的值会被引号包裹成字符串变量。例如

    NSString *attributeName = @"firstName";
    NSString *attributeValue = @"Adam";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ like %@",
    attributeName, attributeValue];
    复制代码

    该谓词格式字符串是 "firstName" LIKE "Adam"

    如果要指定一个动态属性名,则需要用到%K。例如

    predicate = [NSPredicate predicateWithFormat:@"%K like %@",
        attributeName, attributeValue];
    复制代码

    该谓语格式字符串则变成我们想要的 firstName LIKE "Adam"

  1. Creating Predicates Directly in Code(用代码定义一个谓词)

    下面是一段表示(revenue >= 1000000) and (revenue < 100000000)的代码例子。

    NSExpression *lhs = [NSExpression expressionForKeyPath:@"revenue"];
    
    NSExpression *greaterThanRhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInt:1000000]];
    NSPredicate *greaterThanPredicate = [NSComparisonPredicate
                                         predicateWithLeftExpression:lhs
                                         rightExpression:greaterThanRhs
                                         modifier:NSDirectPredicateModifier
                                         type:NSGreaterThanOrEqualToPredicateOperatorType
                                         options:0];
    
    NSExpression *lessThanRhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInt:100000000]];
    NSPredicate *lessThanPredicate = [NSComparisonPredicate
                                      predicateWithLeftExpression:lhs
                                      rightExpression:lessThanRhs
                                      modifier:NSDirectPredicateModifier
                                      type:NSLessThanPredicateOperatorType
                                      options:0];
    
    NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
                                      @[greaterThanPredicate, lessThanPredicate]];
    复制代码
  2. Creating Predicates Using Predicate Templates(使用谓词模板定义谓词)

    这是对通过硬编码定义谓词易造成程序出错与通过代码定义谓词带来的繁琐之间折中之后的方法。例如

    NSPredicate *predicateTemplate = [NSPredicate
    predicateWithFormat:@"lastName like[c] \$LAST_NAME"];
    复制代码

    将上述定义成谓词模板为

    NSExpression *lhs = [NSExpression expressionForKeyPath:@"lastName"];
    
    NSExpression *rhs = [NSExpression expressionForVariable:@"LAST_NAME"];
    
    NSPredicate *predicateTemplate = [NSComparisonPredicate
                                      predicateWithLeftExpression:lhs
                                      rightExpression:rhs
                                      modifier:NSDirectPredicateModifier
                                      type:NSLikePredicateOperatorType
                                      options:NSCaseInsensitivePredicateOption];
    复制代码

    这时我们可以这样来使用它

    NSPredicate *predicate = [predicateTemplate predicateWithSubstitutionVariables:
    [NSDictionary dictionaryWithObject:@"Turner" forKey:@"LAST_NAME"]];
    复制代码

    那么现在这个新的谓词将变成lastName LIKE[c] "Turner"

    使用dictionary进行替换的前提是dictionary中必须包含谓词所指定变量的键值对,所以当我们想要匹配一个null值时,我们必须在dictionary中提供一个null值,例如

    NSPredicate *predicate = [NSPredicate
    predicateWithFormat:@"date = $DATE"];
    predicate = [predicate predicateWithSubstitutionVariables:
    [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"DATE"]];
    复制代码

    这时谓词变成date == <null>

    利用代码直接定义一个谓词其实就是系统帮我们将前面学习的基础语法转换成枚举供我们选择进行创建,避免发生硬编码错误。例如options

    typedef NS_OPTIONS(NSUInteger, NSComparisonPredicateOptions) {
        NSCaseInsensitivePredicateOption, // 字母大小写不敏感,即[c]
        NSDiacriticInsensitivePredicateOption, // 发音不敏感,即[d]
        NSNormalizedPredicateOption, // 即[cd],且系统会对该选项进行性能优化。
    };
    复制代码

    当我们需要组合几个谓词时,使用NSPredicate的子类NSCompoundPredicate会更加方便。

  3. Format String Summary(格式字符串小结)

    被引号包裹的%@%K$VARIABLE会被解释成字符串,因而会阻止任何替换的行为。

    • @"attributeName == %@" : 该谓词会检查属性名attributeNamed的值是否会等于%@所指代的值,可以是NSDate、NSNumber、NSString等。

    • @"%K == %@" : 该谓词会检查键%K的值是否等于%@的值。

    • @"name IN $NAME_LIST" : 该谓词模板会检查键name是否出现在变量$NAME_LIST中。

    • @"`name` IN $NAME_LIST" : 该谓词模板会检查字符串常量`name`是否出现在变量$NAME_LIST中。

    • @"$name IN $NAME_LIST" : 该谓词模板会检查变量$name是否出现在变量$NAME_LIST中。

    • @"%K == `%@`" : 该谓词会检查%K的值是否等于字符串%@的值。

Using Predicate(使用谓词)

  1. Evaluating Predicates(执行谓词)

    这一个简单的例子

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF     IN %@", @[@"Stig", @"Shaffiq", @"Chris"]];
    BOOL result = [predicate evaluateWithObject:@"Shaffiq"];
    复制代码

    注意: 只有支持KVC的类才能使用谓词。

  2. Using Predicates with Arrays(在集合中使用谓词)

    数组与可变数组都支持过滤数组元素的操作。但它们是有区别的。

    • NSArray: 使用filteredArrayUsingPredicate:方法将过滤获取的元素通过一个新的数组返回。
    • NSMutableArray: 使用filterUsingPredicate:方法操作的对象是原数组,只有符合谓词要求的元素才会被保留下来。

    例如

    NSMutableArray *names = [@[@"Nick", @"Ben", @"Adam", @"Melissa"] mutableCopy];
    
    NSPredicate *bPredicate = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
    NSArray *beginWithB = [names filteredArrayUsingPredicate:bPredicate];
    // beginWithB contains { @"Ben" }.
    
    NSPredicate *ePredicate = [NSPredicate predicateWithFormat:@"SELF contains[c] 'e'"];
    [names filterUsingPredicate:ePredicate];
    // names now contains { @"Ben", @"Melissa" }
    复制代码
  3. Using Predicates with Key-Paths(通过键路径使用谓词)

    例如

    NSString *departmentName = ... ;
    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"department.name like %@", departmentName];
    复制代码

    如果是一对多关系,谓词结构会有些许不同。如果想要获取名字的first name是"Matthew"的所有员工的公寓,我们可以使用ANY:

    NSPredicate *predicate = [NSPredicate predicateWithFormat:
    @"ANY employees.firstName like 'Matthew'"];
    复制代码

    如果我们想要知道员工工资大于一定值的员工所在的是哪些部门:

    float salary = ... ;
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY employees.salary > %f", salary];
    复制代码

应用场景

  • 测试代码
        // MARK: - 字符串
        let str = "hello holy! it's so cold today!"
        let p01 = NSPredicate(format: "SELF CONTAINS 'cold'")
        if p01.evaluate(with: str) {
            print("p01: ")
        }

        let p02 = NSPredicate(format: "SELF LIKE[c] 'hello'")
        if p02.evaluate(with: str) {
            print("p02: ")
        }

        let p03 = NSPredicate(format: "SELF LIKE[c] '*ello'")
        if p03.evaluate(with: str) {
            print("p03: ")
        }

        let p04 = NSPredicate(format: "SELF LIKE[c] '?ello'")
        if p04.evaluate(with: str) {
            print("p04: ")
        }

        let p05 = NSPredicate(format: "SELF LIKE '?Ello*'")
        if p05.evaluate(with: str) {
            print("p05: ")
        }

        let p06 = NSPredicate(format: "SELF LIKE[c] 'hello*!'")
        if p06.evaluate(with: str) {
            print("p06: ")
        }
        let p07 = NSPredicate(format: "SELF IN %@", str)
        if p07.evaluate(with: "hello") {
            print("p07: ")
        }

        // MARK: - 集合
        let alice = Person(firstName: "Alice", lastName: "Smith", age: 24, departmentName: "A")
        let bob = Person(firstName: "Bob", lastName: "Jones", age: 13, departmentName: "B")
        let charlie = Person(firstName: "Charlie", lastName: "Smith", age: 20, departmentName: "A")
        let quentin = Person(firstName: "Quentin", lastName: "Alberts", age: 20, departmentName: "C")
        let jack = Person(firstName: "Jack", lastName: "J", age: 18, departmentName: "C")
        let people: NSMutableArray = [alice, bob, charlie, quentin, jack]
        self.people = people

        // 1. 查找lastName为Smith的人
        let p1 = NSPredicate(format: "lastName = 'Smith'")
        let arr1 = people.filtered(using: p1)
        print("arr1: \(arr1)");
        // 2. 查找firstName为某变量的人
        let p2 = NSPredicate(format: "firstName = %@", "Bob")
        let arr2 = people.filtered(using: p2)
        print("arr2: \(arr2)")
        // 3. 查找age >= 18的人
        let p3 = NSPredicate(format: "age >= 18")
        let arr3 = people.filtered(using: p3)
        print("arr3: \(arr3)")
        // 4. 使用可数数组`filter`方法修改原数组
//        let p4 = NSPredicate(format: "age = 18")
//        people.filter(using: p4)
//        print("people: \(people)")
        // 5. 查找住在公寓A的人
        let p5 = NSPredicate(format: "department.name = 'A'")
        let arr5 = people.filtered(using: p5)
        print("arr5: \(arr5)")
        // 6. 是否有人的年龄大于25
        let p6 = NSPredicate(format: "ANY people.age > 25 ")
        if p6.evaluate(with: self) {
            print("p6: 有")
        } else {
            print("p6: 没有")
        }
        // 7. 年龄大于等于20的人
        let p7 = NSPredicate { (evaluatedObject, _) -> Bool in
            return (evaluatedObject as! Person).age >= 20
        }
        let arr7 = people.filtered(using: p7)
        print("arr7: \(arr7)")
        // 8. "%K == %@"
        let p8 = NSPredicate(format: "%K == %@", "lastName", "Smith")
        let arr8 = people.filtered(using: p8)
        print("arr8: \(arr8)")
        // 9.
        let p9t = NSPredicate(format: "lastName = $NAME")
        let p9 = p9t.withSubstitutionVariables(["NAME": "Smith"])
        let arr9 = people.filtered(using: p9)
        print("arr9: \(arr9)")
        // 10. 大于18岁小于20岁
        let lhs = NSExpression(forKeyPath: "age")
        let greaterThanRhs = NSExpression(forConstantValue: 18)
        let greaterP = NSComparisonPredicate(leftExpression: lhs, rightExpression: greaterThanRhs, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.greaterThan, options: NSComparisonPredicate.Options.normalized)

        let lessThanRhs = NSExpression(forConstantValue: 20)
        let lessP = NSComparisonPredicate(leftExpression: lhs, rightExpression: lessThanRhs, modifier: NSComparisonPredicate.Modifier.direct, type: NSComparisonPredicate.Operator.lessThan, options: NSComparisonPredicate.Options.normalized)

        let p10 = NSCompoundPredicate(andPredicateWithSubpredicates: [greaterP, lessP])
        let arr10  = people.filtered(using: p10)
        print("arr10: \(arr10)")

        // MARK: - 验证
        let testPhone = "13422222222"
        let phoneRegex = "^((13[0-9])|(15[^4,\\D])|(18[0,0-9]))\\d{8}$"
        let p21 = NSPredicate(format: "SELF MATCHES %@", phoneRegex)
        if p21.evaluate(with: testPhone) {
            print("是手机号!")
        }

        let testEmail = "jabread007@yahoo.com"
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
        let p22 = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        if p22.evaluate(with: testEmail) {
            print("是邮箱号!")
        }

    }
    
    // 用到的两个类
    class Person: NSObject {
        @objc var firstName: String = ""
        @objc var lastName: String = ""
        @objc var age: Int = 0
        @objc var department: Department

        convenience init(firstName: String, lastName: String, age: Int, departmentName: String) {
            self.init()
            self.firstName = firstName
            self.lastName = lastName
            self.age = age
            self.department.name = departmentName
        }

        override init() {
            department = Department()
            super.init()
        }

        override var description: String {
            return firstName + " " + lastName
        }
    }

    class Department: NSObject {
        @objc var name: String
        init(name: String = "") {
            self.name = name
        }
    }
复制代码
  • Core Data

    NSFetchRequest中有predicate属性,用来对数据进行过滤获取。

  • 验证格式

    主要结合正则表达式的使用。

    1. 邮箱号正则表达式:

    [A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}

    1. 手机号正则表达式:

    ^((13[0-9])|(15[^4,\\D])|(18[0,0-9]))\\d{8}$

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值