Reputation: 12858
I am rebuilding my app's CoreData stack and going to use a unit of work/repository pattern with generics.
My set up is a BaseRepository<T:NSObject>
generic class along with a CoreDataRepository<T:NSManagedObject>
class that inherits from the base repository.
I have four methods for retrieval: getAll
, getAllSorted
, getAllFiltered
, and lastly getAllFilteredSorted
.
The CoreDataRepository
will chain the first three get
methods into the last one, passing in default values.
Here is the definition of the designated method:
func getAllFilteredSorted(predicate:(T) -> Bool, comparer:(T, T) -> Bool) -> T[]
{
let request = NSFetchRequest(entityName: entityName)
let results = context.executeFetchRequest(request, error: nil)
// TODO: Set predicate in Swift
// TODO: Set sort descriptors in Swift
return results as T[]
}
However, I cannot find a way to create NSPredicate
or NSSortDescriptors
with these function closures. NSManagedObjectContext.executeFetchRequest(...)
does not have an overload to take them either.
I know it is possible to sort/filter after the fact, but that reduces the optimization of my CoreData request. I'd prefer to do it beforehand.
Any ideas on how to accomplish this? I don't mind if I can convert the closures to the proper objects, I just do not want to change my function's parameter definitions.
Upvotes: 3
Views: 1883
Reputation: 126127
Credit to @David, but this belongs in an answer, not just a comment.
Don't just do Swifty things because they're Swifty. Bring the right tool for the job.
You can indeed sort and filter with Swift closures:
// NSPredicate:
init(block block: (AnyObject!, [NSObject : AnyObject]!) -> Bool) -> NSPredicate
// NSSortDescriptor:
init(key key: String, ascending ascending: Bool, comparator cmptr: NSComparator)
// where...
typealias NSComparator = (AnyObject!, AnyObject!) -> NSComparisonResult
But don't.
Sorting and filtering with closures in Swift is great when you have data that are already part of an object graph in memory — you can talk to those objects to ask them about their attributes and make decisions based on the replies you get. You can call this a form of imperative programming — you're telling the machine exactly what to do ("give me this data. get this attribute. compare to this other attribute. return NSOrderedAscending
."), and it does exactly what you say.
But when you're working with Core Data, the data you're working with aren't necessarily objects yet — they could be rows in a SQLite database, or thanks to the magic of NSIncrementalStore
subclasses, really just about anything, local or remote. In this case, imperative programming breaks down... you don't want to fetch everything from the database (or wherever it comes from), turn each row into an NSManagedObject
, read attributes from each, etc. (Imagine what would happen if your database were especially large, or your Core Data store were backed by a remote service.)
Instead, what you want is descriptive programming: tell the machine what you generally want done and let it figure out how best to accomplish the result. ("Give me the items whose name contains 'foo' or 'フー', sorted ascending by creation date.") This is what classes like NSSortDescriptor
and NSPredicate
are made for — they're generic (not Generic in a Swift language sense) ways to describe how to sort and filter data. A program (or library like Core Data) is free to read these descriptions and implement them however is most appropriate to the task at hand.
In the case of Core Data running with a local SQLite store, your predicates and sort descriptors get turned into a SQL query, so there's no need to instantiate and work with objects — the sorting and filtering happens much more efficiently on the backend, and constructing objects is necessary only for the results. In fact, I believe at least some of the Core Data APIs will give you an error if you try to do a block/closure-based predicate or comparator with a SQL backend.
Upvotes: 2