swiftrealmmongodb-realm

Explain this code: uses a string as filter function


When reviewing a bit of Swift code in one project I supervise, I came across this:

return (realm?.objects(ExerciseDBObject.self).filter("isDeleted == false")) as! Results<ExerciseDBObject>

What stuck out to me as a JVM/Python/JS programmer was the filter("isDeleted == false") bit. Supposedly, this code works fine: it filters the exercises that are not deleted, but it is a string. How does this work?

I have not worked with Swift, and when googling I just came across the docs on String#filter that seemed to imply that I would normally have written that bit of code as filter({!$0.isDeleted}).

The isDeleted bit of the string refers to a prop on the object. How does Swift avoid binding that to some variable that is also called isDeleted (if such was present, which it was not in this code block)?


Solution

  • It's using:

    // MARK: Filtering
    /**
     Returns a `Results` containing all objects matching the given predicate in the collection.
     - parameter predicateFormat: A predicate format string, optionally followed by a variable number of arguments.
     */
    func filter(_ predicateFormat: String, _ args: Any...) -> Results<Element>
    

    Source: RealmCollectiton

    Under the hood, it's using NSPredicate(format:), it "hides" it by simplifying and avoid writing each time NSPredicate(format: ...), and should use KeyPath too. More explanation on what you can do with that here at Predicate Format String Syntax.

    not

    func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]
    

    on Sequence

    Here a sample code to illustrate/mimic:

    class TestClass: NSObject {
        @objc var title: String
        init(title: String) {
            self.title = title
            super.init()
        }
        override var description: String {
            "TestClass - title: \(title)"
        }
    }
    
    let objects: [TestClass] = [TestClass(title: "Title1"), TestClass(title: "Title2")]
    
    // In a "Swifty way
    let searchedText = "Title1"
    let filtered1 = objects.filter {
        $0.title == searchedText
    }
    print("filtered1: \(filtered1)")
    
    // With a NSPredicate, which is more Objective-C (filtering on NSArray for instance)
    // But also there is a translation with Core-Data, and then fetching optimization
    let filtered2 = objects.filter {
        NSPredicate(format: "title == 'Title1'").evaluate(with: $0)
    }
    print("filtered2: \(filtered2)")
    // Same, but with avoiding hard coding strings
    let filtered3 = objects.filter {
        NSPredicate(format: "%K == %@", #keyPath(TestClass.title), searchedText).evaluate(with: $0)
    }
    print("filtered3: \(filtered3)")
    
    extension Sequence {
        func filter(_ predicateFormat: String, _ args: Any...) -> [Element] {
            let predicate = NSPredicate(format: predicateFormat, argumentArray: args)
            return self.filter({ predicate.evaluate(with: $0) })
        }
    }
    
    // With an extension similar to what does Realm
    let filtered4 = objects.filter("title == 'Title1'")
    print("filtered4: \(filtered4)")
    
    // With an extension similar to what does Realm, and less hard coded string (cf filtered3 construction)
    let filtered5 = objects.filter("%K == %@", #keyPath(TestClass.title), searchedText)
    print("filtered5: \(filtered5)")
    

    With output:

    $>filtered1: [TestClass - title: Title1]
    $>filtered2: [TestClass - title: Title1]
    $>filtered3: [TestClass - title: Title1]
    $>filtered4: [TestClass - title: Title1]
    $>filtered5: [TestClass - title: Title1]