简要理解 - 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]];
    
  1. 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会更加方便。

  2. 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}$

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • 首先,我们需要知道何谓谓词,让我们看看官方的解释: The NSPredicate class is used t...
    jeckHao阅读 1,140评论 1 5
  • NSPredicate类是用来定义逻辑条件约束的获取或内存中的过滤搜索。 可以使用谓词来表示逻辑条件,用于描述对象...
    静守幸福阅读 497评论 0 0
  • 转载自:http://www.cocoachina.com/ios/20160111/14926.html 1、大...
    一笔春秋阅读 2,845评论 0 2
  • 首先,我们需要知道何谓谓词,让我们看看官方的解释:The NSPredicate class is used to...
    旭日飞扬阅读 1,521评论 0 0
  • NSPredicate是一个Foundation类,它指定数据被获取或者过滤的方式。它的查询语言就像SQL的WHE...
    Dean麦兜阅读 373评论 0 2