0

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)?

oligofren
  • 20,744
  • 16
  • 93
  • 180
  • 1
    It's `func filter(_ predicateFormat: String, _ args: Any...) -> Results`, which should use `NSPredicate(format:)` under the hood... – Larme Oct 15 '21 at 17:39
  • Side note: You can't write `filter(!$0.isDeleted)` in Swift. You would need a closure `filter({!$0.isDeleted})` – Leo Dabus Oct 15 '21 at 18:02
  • 2
    As mentioned in an answer this `.filter("isDeleted` is a Realm function, and not a Swift function. Realm is backed by ObjC objects. In a nutshell `.filter("isDeleted` is the same as `NSPredicate("isDeleted == false")` The important bit is that `.filter` is a Realm Object function, in this case the `ExerciseDBObject` class. Which is why the filter 'knows' about the objects properties and won't confuse them with another var. (it won't bind them to some variable because the filter is on that Realm object). – Jay Oct 15 '21 at 18:20
  • @LeoDabus Ah, true. That was a tyop, as they say. Fixed now. – oligofren Oct 15 '21 at 19:38

2 Answers2

3

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]
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Larme
  • 24,190
  • 6
  • 51
  • 81
1

Chances are you are looking at a feature of "Realm", which I think is a tool for working with stored objects, and not a feature of Swift.

The question in this case is "What is realm?.objects(...) returning?". At a guess I would say it's not a String... it's some other object with a custom definition of filter that knows how to parse a String and apply it as a query for a data fetching operation.

This is Polymorphism on the method filter. It can do different things depending on the class of the entity it's called on.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • I wouldn't call this polymorphism, but rather overloading (being nit-picky, I know :p) – Cristik Oct 15 '21 at 17:53
  • I had overloading in the original comment... but Polymorphism is an officially blessed OO term(tm) so I changed it. I would have similar problems deciding between "information hiding" and "encapsulation". ;-) – Scott Thompson Oct 15 '21 at 18:16
  • 1
    A couple of quick things. This is Realm, which is an Object Database backed by MongoDB (NoSQL). Realm is not polymorphic but they are [working on it](https://github.com/realm/realm-java/issues/761). `.filter` is a Realm Collection Object function and is really just an NSPredicate. The `filter` function returns a Realm Results (which is known as a collection) of other realm objects that match the filter. You can see that here `realm?.objects(aRealmObject).filter` the filter is filtering on Realm objects of type `aRealmObject` therefore it's aware of that objects properties. – Jay Oct 15 '21 at 18:39
  • @ScottThompson the difference is that "information hiding" and "encapsulation" are referring to the same thing, while "polymorphism" and "overloading" are two different concepts. – Cristik Oct 17 '21 at 20:06
  • 1
    @Cristik arguing about terminology is largely fruitless. But they are only different concepts depending on whether you view "polymorphism" as being tied to inheritance or whether you consider the term as applying a single selector (to use Objective-C nomenclature) that can be sent to many objects. In C++ Polymorphism is usually described in terms of how it allows a subclass to override a method of the superclass. In Smalltalk Polymorphism describes how the same message can sent to many different objects and elicit different behavior. So it's not as clear cut as you make it out to be. – Scott Thompson Oct 17 '21 at 20:15
  • 1
    `arguing about terminology is largely fruitless` - it might be for us, but it might help other readers. Stackoverflow's goal is to become an encyclopedia of knowledge, and any technical debates help, if kept at a civilised level of course :) And your last comment adds some good arguments to the discussion, arguments which I think worth being added to the answer. – Cristik Oct 18 '21 at 11:37