NSManagedObject.entity() - uninitialized

Viewed 310

I've got an issue with CoreData. I have a generic function like that looks like that:

func fetchRequest<T : NSManagedObject>() -> NSFetchRequest<T>?
{
    let entity = T.entity()
    guard let entityName = entity.name else { return nil }
    let request = NSFetchRequest<T>(entityName: entityName)
    return request
}

No reason it shouldn't work - and in most cases it does without issue. However, sometimes my function returns nil. When I debug it, I get most interesting results:

(lldb) po entity
<uninitialized>

It's interesting because NSManagedObject.entity() returns a non-optional NSEntityDescription

There is no rule as to when it fails - for instance, when I call this function 5 times in a loop it can work the first two times, and then fail the third time. No rules about the class of the entities either.

I've tried something else as well.

extension NSManagedObject
{
    class func concreteFetchRequest<T>() -> NSFetchRequest<T>
    {
        let entityName = String(describing: self)
        return NSFetchRequest(entityName: entityName)
    }
}

Most times that works as well, but sometimes

po entityName
NSManagedObject

despite being used by a concrete NSManagedObject subclass. The bug occurs when this generic function is used down a series of other generic functions, like so

func theTop()
{
    let instance : ConcreteNSManagedObjectSubclass = firstOrCreated() // "Entity description for entity name NSManagedObject not found"
}    

func firstOrCreated<T: NSManagedObject>() -> T
{
    let entity : T
    if let existingEntities() {
        // do stuff
    }
    // do other stuff
}

func existingEntities<T: NSManagedObject>() -> [T]?
{
    guard let request : NSFetchRequest<T> = fetchRequest() else 
    { return [] }
    // do stuff
}

func fetchRequest<T : NSManagedObject>() -> NSFetchRequest<T>?
{
    let entity = T.entity()
    guard let entityName = entity.name else { return nil }
    let request = NSFetchRequest<T>(entityName: entityName)
    return request
}

However, if I modify the code to create a NSFetchRequest at the very top, straight from ConcreteNSManagedObjectSubclass and pass it down - it always works.

func theTop()
{
    let fetchRequest : NSFetchRequest<ConcreteNSManagedObjectSubclass> = ConcreteNSManagedObjectSubclass.concreteFetchRequest()
    let instance : ConcreteNSManagedObjectSubclass = firstOrCreated(fetchRequest) // works fine
}

That looks like a Swift compiler bug to me, like it can't resolve parameter T to a concrete type, so it falls back to NSManagedObject instead - but if that was the case, that shouldn't even compile. Or am I doing something wrong?

1 Answers

Please replace:

let request = NSFetchRequest<T>(entityName: entityName)

with:

let request = T.Type.fetchRequest() as NSFetchRequest<T>

or

let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)

whichever you like.

Related