4
\$\begingroup\$

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
 }
}
asked Nov 14, 2016 at 16:53
\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

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.

answered Nov 17, 2016 at 22:22
\$\endgroup\$
0
\$\begingroup\$

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
 }
}
answered Nov 17, 2016 at 18:13
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.