[Swift4] NSPredicateを楽チンに作成


[SwiftでRealmを使う時のTips(3) NSPredicate編](https://qiita.com/nakau1/items/40865299dacc50d71604)
こちらの記事のextensionがそのままではSwift4で動かないのでSwift4へコンバートしました。

### 基本

いくつか付け加えた関数があります。
コメント付きの関数などが付け加えたものです。

```swift
import Foundation

public extension NSPredicate {
    
    private convenience init(expression property: String, _ operation: String, _ value: Any) {
        
        self.init(format: "%K \(operation) %@", argumentArray: [property, value])
    }
    
}

public extension NSPredicate {
    
    public convenience init(_ property: String, equal value: Any) {
        
        self.init(expression: property, "=", value)
    }
    
    public convenience init(_ property: String, notEqual value: Any) {
        
        self.init(expression: property, "!=", value)
    }
    
    public convenience init(_ property: String, equalOrGreaterThan value: Any) {
        
        self.init(expression: property, ">=", value)
    }
    
    public convenience init(_ property: String, equalOrLessThan value: Any) {
        
        self.init(expression: property, "<=", value)
    }
    
    public convenience init(_ property: String, greaterThan value: Any) {
        
        self.init(expression: property, ">", value)
    }
    
    public convenience init(_ property: String, lessThan value: Any) {
        
        self.init(expression: property, "<", value)
    }
    
    public convenience init(_ property: String, contains query: String) {
        
        self.init(format: "%K CONTAINS %@", argumentArray: [property, query])
    }
    
    public convenience init(_ property: String, beginsWith query: String) {
        
        self.init(format: "%K BEGINSWITH %@", argumentArray: [property, query])
    }
    
    public convenience init(_ property: String, endsWith query: String) {
        
        self.init(format: "%K ENDSWITH %@", argumentArray: [property, query])
    }
    
    public convenience init(_ property: String, valuesIn values: [Any]) {
        
        self.init(format: "%K IN %@", argumentArray: [property, values])
    }
    
    // 両端のタイプが同じであることを保障するためジェネリクスにした。
    public convenience init<T: Any>(_ property: String, between min: T, and max: T) {
        
        self.init(format: "%K IN {%@, %@}", argumentArray: [property, min, max])
    }
    
}

public extension NSPredicate {
    
    public func compound(predicates: [NSPredicate], type: NSCompoundPredicate.LogicalType = .and) -> NSPredicate {
        
        let p = [self] + predicates
        
        switch type {
        case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: p)
        case .or:  return NSCompoundPredicate(orPredicateWithSubpredicates:  p)
        case .not: return NSCompoundPredicate(notPredicateWithSubpredicate:  self.compound(predicates: p))
        }
    }
}

public extension NSPredicate {
    
    // let p: NSPredicate = .not(otherPredicate) のように使えるように
    public static func not(_ predicate: NSPredicate) -> NSPredicate {
        
        return NSCompoundPredicate(notPredicateWithSubpredicate: predicate)
    }
    
    // let p: NSPredicate = .and(otherPredicates) のように使えるように
    public static func and(_ predicates: [NSPredicate]) -> NSPredicate {
        
        return NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
    }
    
    // // let p: NSPredicate = .or(otherPredicate) のように使えるように
    public static func or(_ predicates: [NSPredicate]) -> NSPredicate {
        
        return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
    }
    
    // Boolなキーを簡単に
    public static func `true`(_ property: String) -> NSPredicate {
        
        return NSPredicate(format: "%K = TRUE", property)
    }
    
    // Boolなキーを簡単に
    public static func `false`(_ property: String) -> NSPredicate {
        
        return NSPredicate(format: "%K != TRUE", property)
    }
    
    // Optionalなキーを簡単に
    public static func isNil(_ property: String) -> NSPredicate {
        
        return NSPredicate(format: "%K = NULL", property)
    }
    
    // Optionalなキーを簡単に
    public static func isNotNil(_ property: String) -> NSPredicate {
        
        return NSPredicate(format: "%K != NULL", property)
    }
    
    public func and(_ predicate: NSPredicate) -> NSPredicate {
        
        return self.compound(predicates: [predicate], type: .and)
    }
    
    public func or(_ predicate: NSPredicate) -> NSPredicate {
        
        return self.compound(predicates: [predicate], type: .or)
    }

    public func not(_ predicate: NSPredicate) -> NSPredicate {
        
        return self.compound(predicates: [predicate], type: .not)
    }
}

public extension NSPredicate {
    
    public static var empty: NSPredicate {
        
        return NSPredicate(value: true)
    }
}
```

これだけでは芸がないので、カスタム演算子でNSPredicateを作れるようにしました。

### 演算子

```swift
precedencegroup PredicateDisjunctionPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}
precedencegroup PredicateConjunctionPrecedence {
    associativity: left
    higherThan: PredicateDisjunctionPrecedence
}
precedencegroup PredicatePrecedence {
    higherThan: PredicateConjunctionPrecedence
}

infix operator |=| : PredicatePrecedence
infix operator |!=| : PredicatePrecedence
infix operator |=>| : PredicatePrecedence
infix operator |<=| : PredicatePrecedence
infix operator |>| : PredicatePrecedence
infix operator |<| : PredicatePrecedence

infix operator |<>| : PredicatePrecedence
infix operator ||<| : PredicatePrecedence
infix operator |>|| : PredicatePrecedence


infix operator |<*| : PredicatePrecedence
infix operator |<*>| : PredicatePrecedence

infix operator |+| : PredicateConjunctionPrecedence
infix operator ||| : PredicateDisjunctionPrecedence
prefix operator |!|

public extension NSPredicate {

    static func |+| (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate {

        return .and([lhs, rhs])
    }

    static func ||| (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate {

        return .or([lhs, rhs])
    }

    static prefix func |!| (lhs: NSPredicate) -> NSPredicate {

        return .not(lhs)
    }
}

public struct PredicateKey {

    let name: String

    init(_ name: String) {

        self.name = name
    }
    
    // Equal
    static func |=| (lhs: PredicateKey, rhs: Any) -> NSPredicate {

        return NSPredicate(lhs.name, equal: rhs)
    }
    
    // Not equal
    static func |!=| (lhs: PredicateKey, rhs: Any) -> NSPredicate {

        return NSPredicate(lhs.name, notEqual: rhs)
    }
    
    // Equal or greater than
    static func |=>| (lhs: PredicateKey, rhs: Any) -> NSPredicate {
        
        return NSPredicate(lhs.name, equalOrGreaterThan: rhs)
    }
    
    // Equal or less than
    static func |<=| (lhs: PredicateKey, rhs: Any) -> NSPredicate {
        
        return NSPredicate(lhs.name, equalOrLessThan: rhs)
    }
    
    // Greater than
    static func |>| (lhs: PredicateKey, rhs: Any) -> NSPredicate {
        
        return NSPredicate(lhs.name, greaterThan: rhs)
    }
    
    // Less than
    static func |<| (lhs: PredicateKey, rhs: Any) -> NSPredicate {
        
        return NSPredicate(lhs.name, lessThan: rhs)
    }
    
    // Contains
    static func |<>| (lhs: PredicateKey, rhs: String) -> NSPredicate {
        
        return NSPredicate(lhs.name, contains: rhs)
    }
    
    // Begins with
    static func ||<| (lhs: PredicateKey, rhs: String) -> NSPredicate {
        
        return NSPredicate(lhs.name, beginsWith: rhs)
    }
    
    // Ends with
    static func |>|| (lhs: PredicateKey, rhs: String) -> NSPredicate {
        
        return NSPredicate(lhs.name, endsWith: rhs)
    }
    
    // Values in
    static func |<*| (lhs: PredicateKey, rhs: [Any]) -> NSPredicate {

        return NSPredicate(lhs.name, valuesIn: rhs)
    }
    
    // Between
    static func |<*>| <T: Any> (lhs: PredicateKey, rhs: (T, T)) -> NSPredicate {
        
        return NSPredicate(lhs.name, between: rhs.0, and: rhs.1)
    }
}
```

### 使用法

```swift
class Member: NSObject {
    
    @objc let id: Int
    @objc let name: String
    
    init(id: Int, name: String) {
        
        self.id = id
        self.name = name
        
        super.init()
    }
    
    override var description: String { return "\(id): \(name)" }
}

let names = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Frank", "George", "Helen", "Isaac", "Justin"]
let members = zip(0..., names).map(Member.init)

let p0 = PredicateKey(#keyPath(Member.id)) |>| 2
    |+| PredicateKey(#keyPath(Member.id)) |<| 4
    ||| PredicateKey(#keyPath(Member.id)) |=| 8
// (id > 2 AND id < 4) OR id == 8

let list0 = (members as NSArray).filtered(using: p0)
// [3: Dave, 8: Isaac]

let p1 = |!|(PredicateKey(#keyPath(Member.name)) |<>| "E")
    |+| PredicateKey(#keyPath(Member.name)) |>|| "e"
// (NOT name CONTAINS "E") AND name ENDSWITH "e"

let list1 = (members as NSArray).filtered(using: p1)
// [0: Alice, 2: Charlie, 3: Dave, 6: George]
```

### 雑感
演算子はちょっと微妙ですね。