spogebob92
spogebob92

Reputation: 1484

Fetching entities in CoreData causing a crash

I have a model which looks like this and contains NSManagedObject properties, namely the blendsWith property which is a type of [Tag]:

extension Oil {

@nonobjc public class func fetchRequest() -> NSFetchRequest<Oil> {
    return NSFetchRequest<Oil>(entityName: "Oil")
}

@NSManaged public var blendsWith: [Tag]?
@NSManaged public var color: String?
@NSManaged public var commentsCount: Int64
@NSManaged public var id: Int64
@NSManaged public var imageURL: String?
@NSManaged public var latinName: String?
@NSManaged public var name: String?
@NSManaged public var properties: NSObject?
@NSManaged public var research: String?
@NSManaged public var resourceType: String?
@NSManaged public var viewsCount: Int64

}

public class Oil: NSManagedObject, Codable {

enum CodingKeys: String, CodingKey {
    case resourceType = "resource_type"
    case id, name
    case imageURL = "image_url"
    case color
    case latinName = "latin_name"
    case emotions
    case safetyInformation = "safety_information"
    case fact, research
    case viewsCount = "views_count"
    case commentsCount = "comments_count"
    case blendsWith = "blends_with"
    case foundInBlends = "found_in_blends"
    case properties
    case sourcingMethods = "sourcing_methods"
    case usages
}

required convenience public init(from decoder: Decoder) throws {
    let context = CoreDataHelper.sharedInstance.persistentContainer.viewContext
    guard let entity = NSEntityDescription.entity(forEntityName: "Oil", in: context) else { fatalError() }

    self.init(entity: entity, insertInto: context)

    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.resourceType = try! container.decodeIfPresent(String.self, forKey: .resourceType)!
    self.id = try! container.decodeIfPresent(Int64.self, forKey: .id)!
    self.name = try! container.decodeIfPresent(String.self, forKey: .name)!
    self.imageURL = try! container.decodeIfPresent(String.self, forKey: .imageURL)!
    self.color = try! container.decodeIfPresent(String.self, forKey: .color)!
    self.viewsCount = try! container.decodeIfPresent(Int64.self, forKey: .viewsCount)!
    self.viewsCount = try! container.decodeIfPresent(Int64.self, forKey: .viewsCount)!
    self.commentsCount = try! container.decodeIfPresent(Int64.self, forKey: .commentsCount)!
    self.latinName = try! container.decodeIfPresent(String.self, forKey: .latinName)!

    if let blendsWith = try container.decodeIfPresent([Tag].self, forKey: CodingKeys.blendsWith) {
        self.blendsWith = blendsWith
    }
}

public func encode(to encoder: Encoder) throws {

}

}

Tag looks like this:

extension Tag {

@nonobjc public class func fetchRequest() -> NSFetchRequest<Tag> {
    return NSFetchRequest<Tag>(entityName: "Tag")
}

@NSManaged public var id: Int64
@NSManaged public var name: String?
@NSManaged public var resourceType: String?
@NSManaged public var tagType: String?
@NSManaged public var viewsCount: Int64

}

public class Tag: NSManagedObject, Codable {

enum CodingKeys: String, CodingKey {
    case resourceType = "resource_type"
    case id, name
    case viewsCount = "views_count"
    case tagType = "tag_type"
}

required convenience public init(from decoder: Decoder) throws {
    let context = CoreDataHelper.sharedInstance.persistentContainer.viewContext
    guard let entity = NSEntityDescription.entity(forEntityName: "Tag", in: context) else { fatalError() }

    self.init(entity: entity, insertInto: context)

    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.resourceType = try! container.decodeIfPresent(String.self, forKey: .resourceType)!
    self.id = try! container.decodeIfPresent(Int64.self, forKey: .id)!
    self.name = try! container.decodeIfPresent(String.self, forKey: .name)!
    if let viewsCount = try container.decodeIfPresent(Int64.self, forKey: .viewsCount) {
        self.viewsCount = viewsCount
    } else  {
        self.viewsCount = 0
    }
    if let tagType = try container.decodeIfPresent(String.self, forKey: .tagType) {
        self.tagType = tagType
    } else {
        self.tagType = "lol"
    }
}

public func encode(to encoder: Encoder) throws {

}

}

When I go to fetch the Oil data stored locally, I get this crash:

2018-08-29 20:31:30.602764+0100 EL[27994:14799374] -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980
2018-08-29 20:31:30.603905+0100 EL[27994:14799374] [error] error: exception handling request: <NSSQLFetchRequestContext: 0x608000181ee0> , -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980 with userInfo of (null)
CoreData: error: exception handling request: <NSSQLFetchRequestContext: 0x608000181ee0> , -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980 with userInfo of (null)
2018-08-29 20:31:30.612185+0100 EL[27994:14799374] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[EL initWithCoder:]: unrecognized selector sent to instance 0x60c000679980'

What could be causing this crash?

For reference, my fetching method looks like this:

func getItems<T : NSManagedObject>(predicate : NSPredicate? = nil) -> [T]{
        do {
            let reqest = T.fetchRequest()
            reqest.predicate = predicate
            if let items = try persistentContainer.viewContext.fetch(reqest) as? [T] {
                return items
            } else {
                return [T]()
            }
        } catch let error as NSError {
            print("Could not fetch. \(error), \(error.userInfo)")
            return [T]()
        }
    }

And works like this:

let arrat : [Oil] = CoreDataHelper.sharedInstance.getItems()

Upvotes: 0

Views: 615

Answers (2)

Fabian
Fabian

Reputation: 5358

A Transformable is usually coded by CoreData to Data (stored just as that), and coding an NSManagedObject may do things you do not expect. Decoding it could make things „more“ unexpected. (Coding done by NSKeyedArchiver and NSKeyedUnarchiver automatically on assignment/use.)

If you want to use Transformable then making it an NSManagedObject is pointless due to the argument above. (I at least have no experience with it, and have not heard what use it would have.)

So its usually Transformable OR CoreData-relationships(with NSManagedObjects) to model one relationship in CoreData, but not both to model a single one.

Upvotes: 0

davidethell
davidethell

Reputation: 12018

If you debug this code does it ever step into the Tag init function? It appears the compiler is not seeing your Tag.init function which is most likely due to your Data Model for the Oil object not correctly setting the class type of the blendsWith property.

Check your data model for Oil and make sure that blendsWith is set to the correct type of Tag.

EDIT: For Core Data to pick up your class setting you might need to add the objc flag before your class definition:

@objc(Tag)
public class Tag ...

Upvotes: 0

Related Questions