Swift generic table view data source and delegate, protocol backed NSManagedObjects, cannot obtain entity attribute, runtime error

andrewbuilder

The Problem

break point screenshot

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key active.'

Thanks to @GrahamPerks answer to this SO question, I inserted an exception breakpoint in my code, which now pauses execution at this line...

static var entityActive: Bool {
    return entity.value(forKey: "active") as! Bool // <-- PAUSES AT THIS LINE
}

This obviously needs further explanation...

Background

I'm writing a Core Data app that uses a generic table view data source and delegate, more or less set up per Florian Kugler's book "Core Data", published in 2017 by objc.io.

I have successfully linked three separate UITableViewControllers to this generic data source / delegate. I am using a single main storyboard and these three controllers are linked to three UISplitViewController master/detail views.

I have deleted the automatically generated dataSource and delegate connections within the storyboard UITableViews (although whether I leave or remove these connections, seems to make no difference).

If I comment out the code for the static var entityActive above, the project will successfully Build and Run.

My code uses the UITableViewDelegate method tableView(_, willDisplay:, forRowAt:) to change the .textColor of text and .backgroundColor of cells, based on an attribute "active", stored as a boolean Scalar Type value with each entity.

For clarity, I'm trying to get the "active" attribute (every entity in my data model has a common attribute "active") for each NSManagedObject for the entity. The value of that "active" attribute (Bool true or false) for each entity is then used to format the cell in the if...else statement in the following code.

To attempt to maximise code reuse and minimise code repetition, I have also placed this delegate method in my generic data source / delegate class.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    let entityObjectActive = T.entityActive

    if entityObjectActive == true {
        cell.textLabel?.textColor = UIColor.black
        cell.detailTextLabel?.textColor = UIColor.darkGray
        cell.backgroundColor = UIColor.white
    } else if entityObjectActive == false {
        cell.textLabel?.textColor = UIColor.lightGray
        cell.detailTextLabel?.textColor = UIColor.lightGray
        cell.backgroundColor = UIColor.clear
    }

}

The compiler doesn't complain.

I seem to be able to use the generic type T (representing Core Data entities) to associate the static property entityActive with my instance of entityObjectActive - so this seems to work...

let entityObjectActive = T.entityActive

To confirm, I have the following:

generic data source class....

class MyDataSource<T: Managed, 
                   Delegate: TableViewDataSourceDelegate>: 
                   NSObject, 
                   UITableViewDataSource, 
                   UITableViewDelegate, 
                   NSFetchedResultsControllerDelegate {

    // lots of code...
}

protocol...

protocol Managed: class, NSFetchRequestResult {
    static var entity: NSEntityDescription { get }
    static var entityActive: Bool { get } }
}

and extension...

extension Managed where Self: NSManagedObject {
    static var entity: NSEntityDescription { return entity()  }
    static var entityActive: Bool {
        return entity.value(forKey: "active") as! Bool
    }
}

Attempts at Problem Solving

I've done some reading attempting to resolve my problem, including a review of many blogs (on how to set up generics in general and generic table view data sources) and a lot of SO Q&A, in particular...

How to fix Error: this class is not key value coding-compliant for the key tableView.'

Uncaught exception: This class is not key value coding-compliant

setValue:forUndefinedKey: this class is not key value coding-compliant for the key

It seems that all the SO Q&A are directly related to an issue with IB connections within storyboards. Maybe this is my issue too - but if that is the case it seems to me to be very well hidden.

Any assistance or advice please?

PS: I'm learning Swift after being a long time coder in Obj-C and I'm really struggling with the Generics Protocols Extensions paradigm shift, so it could be that my use of generic types is incorrect?

andrewbuilder

Thanks to those whose comments pointed me towards this solution...

My REVISED protocol Managed...

protocol Managed: class, NSFetchRequestResult {
    static var entity: NSEntityDescription { get }
    var attributeActive: Bool { get }  // <-- REMOVED static 
}

My REVISED extension of Managed...

UPDATE - added var attributeActive to Managed extension...

extension Managed where Self: NSManagedObject {
    static var entity: NSEntityDescription { return entity()  }

    var attributeActive: Bool {
         guard let attribute = self.value(forKey: "active") as? Bool else {
             return false // in case key "active" is not set
         }
         return attribute
    }
}

UPDATE - deleted var attributeActive from managed object extensions as no longer required...

My REVISED extensions for each of my three Core Data Entities (the data of which is displayed in each of the three separate UITableViewControllers)...

extension <<DataModelEntity>>: Managed {    
    public var attributeActive: Bool {
        return self.active
    }

    @NSManaged public var active: Bool
    @NSManaged public var <<OTHER DATA MODEL ENTITY ATTRIBUTES>> //...
    // ...etc.
}

Maybe it is worth noting here for clarity that the Codegen value for each entity in the data model is set to Manual/None, so I have manually1 prepared classes and extensions for each entity.

1 When I write manually, I mean that I used the Create Managed Object Subclass... function under the Editor menu in Xcode and then manually entered my Managed protocol stubs.

Finally, my REVISED generic data source delegate class...

class MyDataSource<T: Managed, 
                   Delegate: TableViewDataSourceDelegate>: 
                   NSObject, 
                   UITableViewDataSource, 
                   UITableViewDelegate, 
                   NSFetchedResultsControllerDelegate {

    // lots of code...

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        let object = fetchedResultsController.object(at: indexPath)
        let objectActive = object.attributeActive

        if objectActive == true {
            cell.textLabel?.textColor = UIColor.black
            cell.detailTextLabel?.textColor = UIColor.darkGray
            cell.backgroundColor = UIColor.white
        } else if entityObjectActive == false {
            cell.textLabel?.textColor = UIColor.lightGray
            cell.detailTextLabel?.textColor = UIColor.lightGray
            cell.backgroundColor = UIColor.clear
        }
    }

    // lots more code...

}

If anyone is still reading this essay, maybe you're interested in the reasons for this solution?

Frankly I'm still figuring out the details myself and plan to add a more concise / accurate reason in the future as my understanding improves regarding swift generics, protocols and extensions, but for now I provide the following...

As pointed out in comments, I was incorrectly attempting to get an entity property based on a data model attribute from an entity description. This is like trying to get the colour or size of a Lego brick by asking the Lego box for the characteristics of one of it's bricks. "Which brick?" might the box ask, if Lego boxes could ask such a thing.

Essentially I made a couple of errors. I didn't understand the effect the static definition had on my variables and I didn't properly understand how my Managed protocol and extension interacted with any classes that adopted that protocol.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Swift Error on communication Protocol/Delegate

Implement delegate using generic protocol in Swift

Cannot display array data from App Delegate in table view cell

Swift generic type that conform to protocol cannot be used to refer protocol?

selecting core data entity from ForEach - error cannot conform to view

How to use a generic protocol as a delegate?

Swift Generic Implementation of protocol throws error `Type does not conform to protocol`

iOS - Pass Data from Swift to ObjC VC using protocol and delegate

How to edit and pass back cell data with delegate and protocol in swift

Swift delegate for a generic class

Spring Data cassandra throws: Cannot obtain where clauses for entity

Core Data: NSManagedObjects stored in array turn into nil when scrolling in table view

cannot find protocol declaration for delegate

SQLAlchemy 'entity' for `add_columns` not backed by a table

Swift Generic struct conform to protocol with associatedType got "Cannot convert return expression of type 'String' to return type 'Key'" Error

Swift generic enums for as TableView data source

Swift protocol delegate returning nil

Protocol Delegate Does not Trigger Swift

Swift 2 - protocol delegate not called

Swift 3 Protocol and Delegate Method?

Protocol Delegate returns nil swift

Swift using generic in protocol

Swift Generic Protocol

Swift generic Serializable protocol

Setting a UITableView data source and delegate in separate file - swift

Swift Array downcasting runtime error when using a protocol

Using associatedtype in a delegate protocol for a generic type

Cannot display array data stored in App Delegate in collection view

Data Source and Delegate Options