HiveBrain v1.2.0
Get Started
← Back to all entries
patternswiftMinor

Swift 3 Generic fetch request extension

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
swiftgenericrequestfetchextension

Problem

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 (_ entityClass:T.Type, sortBy: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [AnyObject]{
    var request: NSFetchRequest

    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
    }
}

Solution

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


request is assigned exactly once, therefore it should be a constant:

let request: NSFetchRequest


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 (_ entityClass:T.Type,
                       sortBy: [NSSortDescriptor]? = nil,
                       predicate: NSPredicate? = nil) throws -> [AnyObject] {

        let request: NSFetchRequest
        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:

extension NSManagedObjectContext{
    func fetchObjects (_ entityClass:T.Type,
                       sortBy: [NSSortDescriptor]? = nil,
                       predicate: NSPredicate? = nil) throws -> [T] {

        let request: NSFetchRequest
        if #available(iOS 10.0, *) {
            request = entityClass.fetchRequest() as! NSFetchRequest
        } 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.

Code Snippets

var request: NSFetchRequest<NSFetchRequestResult>
let request: NSFetchRequest<NSFetchRequestResult>
let entityClassName = NSStringFromClass(entityClass)
let entityName = entityClassName.components(separatedBy: ".").last ?? entityClassName
let entityName = String(describing: entityClass)
if let predicate = predicate {
    request.predicate = predicate
}
if let sortBy = sortBy {
    request.sortDescriptors = sortBy
}

Context

StackExchange Code Review Q#147005, answer score: 6

Revisions (0)

No revisions yet.