1
votes

I have implemented JSON parsing with swift Codable and storing it with Coredata . My Code works fine while storing the JSON and fetching entities which don't have "transformable" attribute. But fetch entity fails and crashes with below log in case where any of it attribute is transformable. I need to keep transformable attribute in case of Custom Objects. Have a look at below core data model classes and fetch request for my implementation. Console log:

-[Decodable.Address initWithCoder:]: unrecognized selector sent to 
instance 0x600000464c40

2018-06-14 11:26:52.768499+0530 Decodable[7870:2988621] [error] error: 
  exception handling request: <NSSQLFetchRequestContext: 0x600000381fb0> 
  , -[Decodable.Address initWithCoder:]: unrecognized selector sent to 
  instance 0x600000464c40 with userInfo of (null)

CoreData: error: exception handling request: 
  <NSSQLFetchRequestContext: 0x600000381fb0> , -[Decodable.Address 
  initWithCoder:]: unrecognized selector sent to instance 0x600000464c40 
  with userInfo of (null)

2018-06-14 11:26:52.777634+0530 Decodable[7870:2988621] *** 
  Terminating app due to uncaught exception 
  'NSInvalidArgumentException', reason: '-[Decodable.Address 
  initWithCoder:]: unrecognized selector sent to instance 
  0x600000464c40'

ViewController.swift

 let request = NSFetchRequest<Clinic>(entityName: "Clinic")
 request.returnsObjectsAsFaults = true

    do
    {
   managedObjectContext = appDelegate.persistentContainer.viewContext

   let result = try managedObjectContext.fetch(request)//***Crashes!!
    }
    catch
    {
        print("no record found")
    }

Clinic+CoreDataClass.swift

import Foundation
import CoreData

public class Clinic: NSManagedObject, NSCoding {


public func encode(with aCoder: NSCoder) {

    aCoder.encode(address, forKey: ClinicCodingKeys.address.rawValue)
  // also encode other class attributes
}

public required convenience init?(coder aDecoder: NSCoder) {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    guard let contextUserInfoKey = CodingUserInfoKey.context,
        let managedObjectContext = 
appDelegate.persistentContainer.viewContext as? 
NSManagedObjectContext,
        let entityDescription = 
NSEntityDescription.entity(forEntityName:"Clinic", in: 
managedObjectContext) else {  fatalError()  }
    self.init(entity: entityDescription, insertInto: 
appDelegate.persistentContainer.viewContext)

    self.address = aDecoder.decodeObject(forKey: 
ClinicCodingKeys.address.rawValue) as? Address
 //*** Also decode other attributes
}

public override func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: ClinicCodingKeys.self)

    try container.encode(remoteid , forKey: .remoteid)
    try container.encode(landline ?? "" , forKey: .landline)
    try container.encode(name ?? "", forKey: .name)
    try container.encode(address , forKey: .address)
    try container.encode(mobile ?? "", forKey: .mobile)
    try container.encode(email ?? "", forKey: .email)
}


public required convenience init(from decoder: Decoder) throws {

    guard let contextUserInfoKey = CodingUserInfoKey.context,
        let managedObjectContext = 
    decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
        let entity = 
    NSEntityDescription.entity(forEntityName:"Clinic", in: 
    managedObjectContext) else {  fatalError()  }

    self.init(entity: entity, insertInto: managedObjectContext)
    let values = try decoder.container(keyedBy: ClinicCodingKeys.self)

    remoteid = try values.decode(Int16.self, forKey: .remoteid)
    landline = try values.decode(String.self, forKey: .landline)
    name = try values.decode(String.self, forKey: .name)
    address = try values.decode(Address.self, forKey: .address)
    mobile = try values.decode(String.self, forKey: .mobile)
    email = try values.decode(String.self, forKey: .email)
}
}

Clinic+CoreDataProperties.swift

import Foundation
import CoreData


extension Clinic: Codable {

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

@NSManaged public var remoteid: Int16
@NSManaged public var landline: String?
@NSManaged public var name: String?
@NSManaged public var address: Address?//***** Transformable attribute 
    //for core data model class Address
@NSManaged public var mobile: String?
@NSManaged public var email: String?

enum ClinicCodingKeys: String, CodingKey {
    case remoteid = "_id"
    case landline
    case name
    case address
    case mobile
    case email
}
}

Address+CoreDataClass.swift

import Foundation
import CoreData


public class Address: NSManagedObject {


public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(address ?? "" , forKey: .address)
    try container.encode(area ?? "" , forKey: .area)
    try container.encode(postalcode ?? "" , forKey: .postalcode)
    try container.encode(city ?? "" , forKey: .city)
    try container.encode(state ?? "" , forKey: .state)
    try container.encode(country ?? "" , forKey: .country)
    try container.encode(lat ?? "" , forKey: .lat)
    try container.encode(lng ?? "" , forKey: .lng)
}

public required convenience init(from decoder: Decoder) throws {

    guard let contextUserInfoKey = CodingUserInfoKey.context,
        let managedObjectContext = 
decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
        let entity = 
NSEntityDescription.entity(forEntityName:"Address", in: 
managedObjectContext) else {  fatalError()  }

    self.init(entity: entity, insertInto: managedObjectContext)
    let values = try decoder.container(keyedBy: CodingKeys.self)
    address = try values.decode(String.self, forKey: .address)
    area = try values.decode(String.self, forKey: .area)
    postalcode = try values.decode(String.self, forKey: .postalcode)
    city = try values.decode(String.self, forKey: .city)
    state = try values.decode(String.self, forKey: .state)
    country = try values.decode(String.self, forKey: .country)
    lat = try values.decode(String.self, forKey: .lat)
    lng = try values.decode(String.self, forKey: .lng)

}
}

Address+CoreDataProperties.swift

import Foundation
import CoreData

extension Address : Codable{

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

@NSManaged public var address: String?
@NSManaged public var area: String?
@NSManaged public var postalcode: String?
@NSManaged public var city: String?
@NSManaged public var state: String?
@NSManaged public var country: String?
@NSManaged public var lat: String?
@NSManaged public var lng: String?

enum CodingKeys: String, CodingKey
{
    case address
    case area
    case postalcode = "postal_code"
    case city
    case state
    case country
    case lat
    case lng
}
}

I have tried implementing initWithCoder: in Clinic+CoreDataClass.swift but it gives error - Cannot invoke initWith(entity: insertInto:). And I need to implement initwith(entity: insertinto:)

1
can you also post the implementation for Address? - Rohan Bhale
@RohanBhale added the code of Model class "Address". - Shirish
also implement NSCoding protocol here. these unrecognised selectors error belong missing methods of the NSCoding protocol - Rohan Bhale
But Codable is what we got for replacing old NSCoding (check [medium.com/if-let-swift-programming/…). I have Implemented Codable . So why NSCoding protocols are needed. This same implementation works fine when there's no transformable attribute in model class/Entity. There's no NSCoding protocol implemented there. But when Custom objects are passed as type for attribute(here "Address" in Clinic+CoreDataProperties.swift), it crashes. - Shirish
Codable is a Swift defined stuff. CoreData is developed using objc runtime. Codable is fine for parsing stuff. In order to save data though you need to use NSCoding protocol. If you look closely even the method definitions are different for encoding and decoding - Rohan Bhale

1 Answers

0
votes

You are fetching it wrongly NSFetchRequest<Clinic>(entityName: "Clinic") and you are not checking entity records before saving into result variable.

  let request = NSFetchRequest<Clinic>(entityName: "Clinic")
     request.returnsObjectsAsFaults = true

        do
        {
       managedObjectContext = appDelegate.persistentContainer.viewContext

       let result = try managedObjectContext.fetch(request)//***Crashes!!
        }
        catch
        {
            print("no record found")
        }

Replace your code with fetchData() function and make sure Entity name is same.

func fetchData()   {
            let appDelegate = UIApplication.shared.delegate as! AppDelegate
            let context = appDelegate.persistentContainer.viewContext
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Clinic")
            do{
                 let records = try context.fetch(fetchRequest)

                if let records = records as? [NSManagedObject]{
                    if !records.isEmpty{
                        var result:[NSManagedObject] = records
                        print("coreData result : \(records)")
                    }else{
                       print("No record in Clinic entity")
                    }

                }

            }catch{
                print("Error")
            }
        }