簡要理解 - NSPredicate

簡介

NSPredicate是基礎庫中用來過濾獲取數據的類,相似於SQL中的where語句,但它提供了更爲天然且高級的語言,在數據集合的查詢上去定義了邏輯條件語句。git

直接展現NSPredicate的用法可能比抽象的講解更容易理解,但在這以前,咱們先來學習其基礎的語法。正則表達式

基礎語法

  • Parser Basics(謂語字符串的基本解析)

    謂詞格式字符串對關鍵字、括號敏感,對空格不明感,且不會進行語義類型檢測。express

    $ :以$符號開頭聲明變量,例如 $VARIABLE_NAME數組

    %K :屬性名性能優化

    %@ :屬性值bash

    使用%@佔位符只能表示一些表達式,而不能去表示一個謂詞,不然會形成程序異常。less

  • Basic Comparisons(基本比較運算)

    =、== : 判斷左邊表達式是否等於右邊表達式。ide

    >=、 => : 判斷左邊表達式是否大於或等於右邊表達式。性能

    <=、=< : 判斷左邊表達式是否小於或等於右邊表達式。學習

    > : 判斷左邊表達式是否大於右邊表達式。

    < : 判斷左邊表達式是否小於右邊表達式。

    !=、<> : 判斷左邊表達式是否不等於右邊表達式。

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

相關文章
相關標籤/搜索