SUBQUERY Is Not That Scary
And yes — it’s second parameter doesn’t have to be named „$x”
Let’s say that you’re new in the project. After a short onboarding you are getting your first task, here it is:
Add search functionality to the contacts
In description you see:
Search results should include all contacts that match the search query by:
Nothing easier than that
UI with all delegate methods is implemented, you know some Core Data,
NSFetchedResultsController is already in place. All you need is to supply a
NSFetchRequest — what can be easier than that?
Boom, done, let’s give it a try!
** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<NSDictionaryMapNode 0x608002079b40> valueForUndefinedKey:]: this class is not key value coding-compliant for the key city.’
Aaaaa… not that fast.
city is not a property of a
Contact. Actually you’d be safe and figured it out earlier if you’d use a #keyPath expression (you definitely should), but that’s not my goal in this post. We need to dig a bit deeper to understand what happened.
Ok, so here’s the model:
city is a property of the
Address entity. Additionally single contact can have multiple addresses — they are all placed in the
NSFetchRequest is configured to iterate over (and return)
Contact objects, so with a regular
NSCompoundPredicate( orPredicateWithSubpredicates: […, NSPredicate(“SELF.city == %@”)] we cannot achieve what we need. We need to iterate over all
addresses. We need some kind of a predicate that within a given
Contact will iterate over all addresses to decide if any of them matches our search query (via
city property). This is exactly the situation where a predicate with a
SUBQUERY comes handy and is the best possible performance-wise solution (see comparison of results using two other ways in my playground).
For many people
SUBQUERY is an obscure topic but actually the concept behind it is very simple:
SUBQUERYis a kind of a double loop (or a double filter) that works deep down in the Core Data stack to work out a set of objects that matches a given criteria.
How to understand it?
Let’s for a moment step back from managed objects and Core Data and see what logic do we need to have in place to get the results we expect. Let’s also simplify our task a bit and focus on the hard part — matching against
city in the
Our goal is to find all contacts that have address where city matches our query, so if we’d work on a regular collection the most basic and explicit implementation we can imagine is:
In lines 1–3 we define our collection to be filtered and create initial set of results. We expect a
results collection to be a subset of
contacts, so we may simplify our code by applying a
filter method. Let’s rewrite it like this:
In lines 4–9 we have the same kind of situation again — we have an inner filter here — we expect to get a subset of
addressesMatchingQuery based on the value of the expression in line 6. Let’s use a
filter method again:
Let’s take it to the extreme and compact it even more:
Great, this is pretty clear — return all contacts that have more than zero addresses which
city property is equal to
searchQuery. Let’s leave it like this and now get back to our
NSFetchRequest and Core Data and see how our implementation actually matches what
SUBQUERY has to offer.
How does one relate to another?
SUBQUERY is functionally equivalent to our non-Core Data implementation (if applied to our
The first parameter of the
SUBQUERY is a relation name, second one is a temporary variable name that is used to define a predicate — a third parameter — that is used to match against every object from the relation.
There is a reason why I’ve taken you through that non Core Data implementation and then shown you this. That’s because two constructs are doing the same thing and are almost identical syntactically. If we give our code some specific layout we can see it more clearly (be sure to zoom in the image below):
SUBQUERY behaves like a double filter. It uses three parameters:
- A relation name to make inner filtering on
- A variable, that starts with the
$symbol (an equivalent of the non-Core Data inner filter’s closure parameter)
- A predicate/condition that is evaluated agains that variable
I bet you can easily tweak our initial implementation now to leverage
SUBQUERY and make a good first impression on your new team colleagues. Sometimes things are really simple, but it all depends on the right perspective.
If you want to play with the code, or see the benchmark of three different strategies of matching that I mentioned before you can find my playground here. Good luck 🙂
I’m Maciek Czarnik
iOS Developer, musician, maker. Passionate about building beautiful, robust, useful and user-friendly apps.