1. Creating Entity
Open the Xcode model file, MyCalculator.xcdatamodeld in my case and create the entity as shown in the image below.
After that, select the model file again and choose Editor menu then Create NSManagedObject Subclass.... Xcode will then generate two files below.
HistoryMO-CoreDataClass.swift
import Foundation
import CoreData
public class HistoryMO: NSManagedObject {
}
HistoryMO-CoreDataProperties.swift
import Foundation
import CoreData
extension HistoryMO {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "HistoryMO")
}
@NSManaged public var operations: [AbstractExpressionMO]
@NSManaged public var result: Double
}
2. Create the Custom Classes
Core Data converts an object into a type that it can store when saving and convert it back to the original object when restoring from the file system. A transformable attribute requires a NSValueTransformer subclass. The default transformer is NSKeyedUnarchiveFromDataTransfer class, which converts the object to and from NSData but the transformed object must conform to NSCoding protocol. If it does not conform to NSCoding protocol, we should write a custom NSValueTransformer subclass.
In my example, the custom classes I created conform to NSCoding protocol.
import Foundation
import CoreData
public class AbstractExpressionMO : NSObject, NSCoding {
public override init() {
super.init()
}
public required init?(coder aDecoder: NSCoder) {
self.init()
}
public func encode(with aCoder: NSCoder) {
}
}
The AbstractExpressionMO, as its name implies, is not intended for directly persisted. Since Swift does not support abstract class like in Java, I declared it as a class with empty methods. It extends from NSObject because Core Data requires the persisted object to be of type NSObject.
I added an initializer without parameter for the subclasses to call because Swift requires that all designated initializers of a subclass must call a designated initializer of its superclass.
public class NumberMO : AbstractExpressionMO {
fileprivate static var attrValue:String = "value"
var value:Double?
public init(_ value:Double?) {
super.init()
self.value = value
}
public convenience required init?(coder aDecoder: NSCoder) {
let obj = aDecoder.decodeObject(forKey: NumberMO.attrValue)
if obj != nil {
self.init(obj as! Double)
} else {
// A void error when a column value is null
self.init(nil)
}
}
public func decode(coder aDecoder: NSCoder) {
let obj = aDecoder.decodeObject(forKey: NumberMO.attrValue)
if obj != nil {
value = obj as! Double
}
}
public override func encode(with aCoder: NSCoder) {
aCoder.encode(value, forKey: NumberMO.attrValue)
}
}
In NumberMO subclass, the convenience keyword was added for the initializer init(coder:) to force it to call one of the NumberMO's designated initializers. In my case, I want it to call init(_:Double?) initializer because I don't have to duplicate the codes to assign the value to a property.
I used NSCoder.decodeObject(forKey:) instead NSCoder.decodeDouble(forKey:) to avoid the app crash if the column value in the store is null.
public class OperatorMO : AbstractExpressionMO {
fileprivate static var attrTag:String = "tag"
var tag:Int?
public init(_ tag:Int?) {
super.init()
self.tag = tag
}
public convenience required init?(coder aDecoder: NSCoder) {
let tag = aDecoder.decodeObject(forKey: OperatorMO.attrTag)
if tag != nil {
self.init(tag as! Int)
} else {
// A void error when a column value is null
return nil
}
}
public override func encode(with aCoder: NSCoder) {
aCoder.encode(tag, forKey: OperatorMO.attrTag)
}
}
Then, I just created the instances of NumberMO and OperatorMO classes and added them to the HistoryMO's operations properties and persisted them along with the HistoryMO object.
For how to write the object to the store, read this post.
No comments:
Post a Comment