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