Wednesday, January 23, 2019

Parent-Child Relationship in Core Data

I'm writing this example project (CoreDataTest) using Xcode 9 and Swift 3. I have two classes, one is parent and another one is child. I want to add an array of child objects as a property of the parent class and then save them to the store.

1. CoreDataTest.xcdatamodeld 


Parent entity (Equation)




Child entity (OperatorLog)



2. The Model Classes


I let Xcode generate the swift files below.

Equation+CoreDataClass.swift

import Foundation
import CoreData

@objc(Equation)
public class Equation: NSManagedObject {

}
 

Equation+CoreDataProperties.swift

import Foundation
import CoreData

// MARK: Generated accessors for operatorLogs
extension Equation {
 @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "Equation")
    }

    @NSManaged public var operatorLogs: NSSet?

    @objc(addOperatorLogsObject:)
    @NSManaged public func addToOperatorLogs(_ value: OperatorLog)
    

    @objc(removeOperatorLogsObject:)
    @NSManaged public func removeFromOperatorLogs(_ value: OperatorLog)

    @objc(addOperatorLogs:)
    @NSManaged public func addToOperatorLogs(_ values: NSSet)
    
    @objc(removeOperatorLogs:)
    @NSManaged public func removeFromOperatorLogs(_ values: NSSet)
}
 

OperatorLog+CoreDataClass.swift

import Foundation
import CoreData

public class OperatorLog: NSManagedObject {
    
}
 

OperatorLog+CoreDataProperties.swift

import Foundation
import CoreData

extension OperatorLog {

    @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "OperatorLog")
    }
    
    @NSManaged public var operatorTag: Double
    @NSManaged public var operand: Double
    @NSManaged public var result: Double
}
 

3. The Datastore's methods


I created a class named Datastore and added the following method:

    func saveEquation(_ operatorLogs: [OperatorLog]) throws -> Equation {
        var savedEquation:Equation!
        context?.performAndWait {
            savedEquation = NSEntityDescription.insertNewObject(forEntityName: "Equation", into: context!) as! Equation
            savedEquation.addToOperatorLogs(NSSet(arrayLiteral: operatorLogs))
        }
        
        var error: Error?
        if (context?.hasChanges)! {
            do {
                try context?.save()
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        return savedEquation
    }
 

4. Calling the method


In the ViewController's viewDidLoad() method, I retrieved the OperatorLog objects from the store I previously saved and added it to the Equation object then persisted the Equation object.

        do {
            let operatorLogs:[OperatorLog] = try datastore!.getOperatorLogs()
            try datastore?.saveEquation(operatorLogs)
        } catch let e {
            print("\(e.localizedDescription)")
        }
        
        do {
            let entities:[Equation] = try datastore!.getEquations()
            for entity in entities {
                var equation = ""
                for child in entity.operatorLogs! {
                    let opLog = child as! OperatorLog
                    equation.append(String(describing: opLog.operand))
                    equation.append(String(describing: opLog.operatorTag))
                }
                print("> Equation: \(equation)")
            }
        } catch let e {
            print("\(e.localizedDescription)")
        }
 

Error


The example above is the correct way to implement parent-child relationship in Core Data but the app crashed when Swift tried to save changes to the store. Here is the error message:
 "CoreDataTest[31184:16565250] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtGCs23_ContiguousArrayStorageC12CoreDataTest11OperatorLog_ _isKindOfEntity:]: unrecognized selector sent to instance"

I looked up on the net and they said that it was a bug with Core Data. Apple hasn't solved it yet up until now.

Solution


According to the book "iOS Programming - The Big Nerd Ranch Guide, 5th Edition", the author added the child property manually and used Set instead of NSSet as the type of the child object. Then, I left the Equation's operatorLogs property as NSSet but changed the type of the parameter of the generated method addToOperatorLogs(_:) to Set<OperatorLog> and it worked.
@NSManaged public func addToOperatorLogs(_ values: Set<OperatorLog>)

Of course, I also had to update the saveEquation() method too on the following line.
savedEquation.addToOperatorLogs(Set(operatorLogs))





No comments:

Post a Comment