I implemented an extension for the NSManagedObjectContext
that takes any CoreData class as a parameter and performs a fetch request on it.
I am new to CoreData and would very much appreciate any sort of feedback for this function
extension NSManagedObjectContext{
func fetchObjects <T: NSManagedObject>(_ entityClass:T.Type, sortBy: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [AnyObject]{
var request: NSFetchRequest<NSFetchRequestResult>
if #available(iOS 10.0, *) {
request = entityClass.fetchRequest()
} else {
let entityClassName = NSStringFromClass(entityClass)
let entityName = entityClassName.components(separatedBy: ".").last ?? entityClassName
request = NSFetchRequest(entityName: entityName)
}
var fetchRequestError: Error?
request.returnsObjectsAsFaults = false
if let predicate = predicate {
request.predicate = predicate
}
if let sortBy = sortBy {
request.sortDescriptors = sortBy
}
var fetchedResult: [T]?
do {
fetchedResult = try self.fetch(request) as? [T]
}
catch let error {
fetchRequestError = error
//print(fetchRequestError)
}
guard let entityArray = fetchedResult else {
throw fetchRequestError!
}
return entityArray
}
}
2 Answers 2
Your code looks good, but there are some things which can be simplified.
Additionally, the method can be improved to return a array of the
actual managed object subclass type instead of AnyObject
.
Simplifications and small improvements
var request: NSFetchRequest<NSFetchRequestResult>
request
is assigned exactly once, therefore it should be a constant:
let request: NSFetchRequest<NSFetchRequestResult>
let entityClassName = NSStringFromClass(entityClass) let entityName = entityClassName.components(separatedBy: ".").last ?? entityClassName
can be simplified to
let entityName = String(describing: entityClass)
which returns the unqualified class name of the given class
(in contrast to String(reflecting:)
which returns the fully qualified
class name including the module name).
if let predicate = predicate { request.predicate = predicate } if let sortBy = sortBy { request.sortDescriptors = sortBy }
The predicate
and sortDescriptors
of NSFetchRequest
are optionals, so there is no need to conditionally unwrap
the given parameters. You can assign them directly:
request.predicate = predicate
request.sortDescriptors = sortBy
fetchedResult = try self.fetch(request) as? [T]
We know that the fetch request returns an array of T
objects,
the cast cannot fail. Therefore the second half of the method
can be reduced to
do {
let fetchedResult = try self.fetch(request) as! [T]
return fetchedResult
} catch let error {
print(error.localizedDescription)
throw error
}
making the fetchRequestError
variable obsolete. And if you don't
need to print the error locally then no do/catch is needed at all:
let fetchedResult = try self.fetch(request) as! [T]
return fetchedResult
A possible error will be re-thrown to the caller of the method.
Putting it all together, the method now looks like this:
extension NSManagedObjectContext{
func fetchObjects <T: NSManagedObject>(_ entityClass:T.Type,
sortBy: [NSSortDescriptor]? = nil,
predicate: NSPredicate? = nil) throws -> [AnyObject] {
let request: NSFetchRequest<NSFetchRequestResult>
if #available(iOS 10.0, *) {
request = entityClass.fetchRequest()
} else {
let entityName = String(describing: entityClass)
request = NSFetchRequest(entityName: entityName)
}
request.returnsObjectsAsFaults = false
request.predicate = predicate
request.sortDescriptors = sortBy
let fetchedResult = try self.fetch(request) as! [T]
return fetchedResult
}
}
Returning a "correctly" typed array
The return type is [AnyObject]
, which means that the return value has to be cast to the actual managed object subclass type
let objects = try context.fetchObjects(YourEntity.self) as! [YourEntity]
This can be improved by changing the return type to [T]
and using
a typed fetched request NSFetchRequest<T>
:
extension NSManagedObjectContext{
func fetchObjects <T: NSManagedObject>(_ entityClass:T.Type,
sortBy: [NSSortDescriptor]? = nil,
predicate: NSPredicate? = nil) throws -> [T] {
let request: NSFetchRequest<T>
if #available(iOS 10.0, *) {
request = entityClass.fetchRequest() as! NSFetchRequest<T>
} else {
let entityName = String(describing: entityClass)
request = NSFetchRequest(entityName: entityName)
}
request.returnsObjectsAsFaults = false
request.predicate = predicate
request.sortDescriptors = sortBy
let fetchedResult = try self.fetch(request)
return fetchedResult
}
}
This is now called as
let objects = try context.fetchObjects(YourEntity.self)
and objects
is an array of YourEntity
without additional cast.
It looks solid to me, but I can't comment on performance-wise. My issue was trying to read it. It took me a few minutes of figuring it out before I had to reformat it in Xcode:
extension NSManagedObjectContext {
func fetchObjects <T: NSManagedObject>(_ entityClass: T.Type,
sortBy: [NSSortDescriptor]? = nil,
predicate: NSPredicate? = nil)
throws -> [AnyObject] {
var request: NSFetchRequest<NSFetchRequestResult>
if #available(iOS 10.0, *) {
request = entityClass.fetchRequest()
} else {
let entityClassName = NSStringFromClass(entityClass)
let entityName = entityClassName.components(separatedBy: ".").last
?? entityClassName
request = NSFetchRequest(entityName: entityName)
}
var fetchRequestError: Error?
request.returnsObjectsAsFaults = false
if let predicate = predicate {
request.predicate = predicate
}
if let sortBy = sortBy {
request.sortDescriptors = sortBy
}
var fetchedResult: [T]?
do {
fetchedResult = try self.fetch(request) as? [T]
}
catch let error {
fetchRequestError = error
//print(fetchRequestError)
}
guard let entityArray = fetchedResult else {
throw fetchRequestError!
}
return entityArray
}
}
I know your style is definitely different from mine, ( I would wrap each thing up in a labeled do block, or comment each section based on its purpose) but I think that mine is considerably easier to read, and I tried to respect your coding style and the Swift conventions.
A couple places you had inconsistent spacing and linebreaks
My two biggest issues were the single line func declaration, and your nil coalesce.. I didn't realize until after I'd reformatted that you had actually assigned a value to request
after that (it all looked like one line to me)
Just my 2c.. I know I tend to over-linebreak in my code, but for me, it took me a while to figure out what was going on in your code initially. A few extra breaks can help emphasize when you are starting a new logic sequence, or when you are setting one one up vs actually defining it.
Overall an "A" I think! :D Not that mine is any better. Just my take on it.
Hope this helps.
PS, here is my personal rendition of it:
extension NSManagedObjectContext {
func fetchObjects<T: NSManagedObject> (_ entityClass: T.Type,
sortBy: [NSSortDescriptor]? = nil,
predicate: NSPredicate? = nil)
throws -> [AnyObject] {
// Initial request:
var request: NSFetchRequest<NSFetchRequestResult>
if #available(iOS 10.0, *) {
request = entityClass.fetchRequest()
}
else {
let entityClassName = NSStringFromClass(entityClass)
let entityName = entityClassName.components(separatedBy: ".").last
?? entityClassName
request = NSFetchRequest(entityName: entityName)
}
// Fetch request:
var fetchRequestError: Error?
request.returnsObjectsAsFaults = false
if let predicate = predicate { request.predicate = predicate }
if let sortBy = sortBy { request.sortDescriptors = sortBy }
var fetchedResult: [T]?
do { fetchedResult = try self.fetch(request) as? [T] }
catch let error { fetchRequestError = error }
// Returner:
guard let entityArray = fetchedResult
else { throw fetchRequestError! }
return entityArray
}
}