Structuring CoreData for Efficiency and Ease: Part 2

Part 2: I just hate NS — Building the Query Builder

Ritwik P
5 min readJan 13, 2023
Photo by Gabriel Heinzer on Unsplash

If you have been following the series and are coming from PART 1 , First things first: Separate Logic from Crap!, you are well aware of my hatred towards the verboseness of code in CoreData. Don’t get me wrong I am a fan of the framework. Just not the legacy code that makes coding so damn complicated. With the introduction of Combine Framework, working with CoreData has been made extraordinarily easy — if and that’s a big IF your model is extremely simple and the processing behind the scenes isn’t complicated. Fetching data for direct display is a breeze, but manipulating data behind the scenes — that’s another ball game altogether. If only there was a way to circumvent those pesky NSPredicates and NSFetchRequest<NSFetchRequestResult> as! NSManagedObject 🤣. Even typing it out seems so much work.

Aut Viam Inveniam Aut Faciam

I will either find a way or make one.

Let us find out if we can make one!

Codifying the model

I am not sure about you, but remembering the string names and entity descriptions and entity names given a fairly complex model, is a bitch! Can’t really force myself to think of the time when I hadn’t had to go through my code over and over again because of a pesky little typo somewhere in the string name or the NSFetchRequest. God! I hate those pesky fetch requests!

So we begin by codifying our model into enums. Double check it. Triple check it … and then … then forget about it!

Let’s make that model for us! Refer to the Part 1 to see the model for this series. It’s quite simple, don’t worry!

Create a new file and name it Model or whatever floats your boat!

enum Entity {
case Item
case Category

var entityName: String {
switch self {
case .Item:
return "CDItem"
case .Category:
return "CDCategory"
}
}
}

enum Item {
case name
case id
case ofCategory

var attributeName: String {
switch self {
case .name:
return "name"
case .id:
return "id"
case .ofCategory:
return "ofCategory"
}
}
}

enum Category {
case name
case id

var attributeName: String {
switch self {
case .name:
return "name"
case .id:
return "id"
}
}
}
}

And that’s it! A nested enum. Now we won’t keep making typing mistakes (Probably) in typing out Predicates or Entity Names.

Now the pesky little code which we simplified in the last part to,

let predicate = NSPredicate(format: "id == %@", id)
guard let items = try? await Item.read(with: predicate) else { return }

Becomes,

let predicate = NSPredicate(format: "\(Model.Item.id.attributeName) == %@", id)
guard let items = try? await Item.read(with: predicate) else { return }

Not much of an improvement if you ask me. But I would at least not be debugging mistyping “id” as “Id” or anything of that sort!

However, I still hate the NS. I hate all NSssssss!

So we take it another step further. We will try and build a declarative Query Builder to not have to type those NSsssss ever again.

Query Builder

Create another swift file and name is Query. Fill it up with the requirements of a fetch request

class Query {
var fetchRequest: NSFetchRequest<NSFetchRequestResult>!
var predicates = [NSPredicate]()
var compoundPredicate: NSCompoundPredicate!
}

This should cover almost every requirement that a fetch request shall posses to fetch data from context. If you have other requirements, just code them here.

Easy right?

Wait for it!

If you are aware of declarative programming the next part needs no explanation. Add the following code to the class Query.

class Query {
var fetchRequest: NSFetchRequest<NSFetchRequestResult>!
var predicates = [NSPredicate]()
var compoundPredicate: NSCompoundPredicate!

@discardableResult func entityName(_ entityName: String) -> Self {
fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
return self
}

@discardableResult func equals(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) == %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}

@discardableResult func greaterThan(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) > %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}

@discardableResult func greaterThanOrEqualTo(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) =>= %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}

@discardableResult func lessThan(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) < %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}

@discardableResult func lessThanOrEqualTo(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) <= %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}

@discardableResult func notEqualTo(attributeName: String, value: Any) -> Self {
predicates.append(NSPredicate(format: "\(attributeName) != %@", value as! CVarArg))
compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
return self
}
}

Daunting??

Don’t worry. It’s quite simple. @discardableResult is a modifier that allows the mutation of the class. In essence, it allows one to mutate the values inside the class on the fly. But most importantly, it allows for declarative coding.

Which means that now, the old code, which I hate from

let context = PersistenceController.shared.container.newBackgroundContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
fetchRequest.predicate = NSPredicate(format: "id == %@", "id")

do {
let items = try context.fetch(fetchRequeast) as! [Item]
} catch {
fatalError()
}

Transformed to

let predicate = NSPredicate(format: "id == %@", id)
guard let items = try? await Item.read(with: predicate) else { return }

Further transformed to

let predicate = NSPredicate(format: "\(Model.Item.id.attributeName) == %@", id)
guard let items = try? await Item.read(with: predicate) else { return }

Now transforms to

var query = Query()
.entityName(Model.Entity.Item.entityName)
.equals(attributeName: Model.Item.name.attributeName, value: "id")
guard let items = try? await Item.read(with: query) else { return }

We still have to rewrite read(with: ) but that is what it will eventually transform into!

No more pesky NSsssssssss! What’s more, think about a query like, name >= A and name <= E, which would now be

var query = Query()
.entityName(Model.Entity.Item.entityName)
.greaterThan(attributeName: Model.Item.name.attributeName, value: "A")
.lessThanOrEqualTo(attributeName: Model.Item.name.attributeName, value: "E")
guard let items = try? await Item.read(with: query) else { return }

Which for me is loads better and makes much more logical sense to work with.

We have used compound predicate using AND functionality but the same query can be extended, using operator overloading, for logical OR. More declarative functions can be added for specific needs of the project, making the coding of projects faster.

In PART 1 we addressed the problem of contexts and concurrency, by creating a value layer composed by mirroring structs. In this part we have built ourself a nifty Query Builder for making the code more logical to write and interpret.

Moving forward we would look into, relationships and implementations that would be efficient to use and now easier to implement.

Till next time!

Happy Coding!

--

--

Ritwik P

I am an author by heart, bureaucrat by accident, coder by choice and a human by chance ;)