Fetching from CoreData and casting - swift

When fetching data from CoreData the results is an array of AnyObjects. When I cast this array to the related class, I can use the data with no problems.
My question is, I want to do something when the objects are being initialized but I don't know where and when the objects are getting initialised after it has been fetched from CoreData.
A sample of how I get the data from CoreData:
let request = NSFetchRequest(entityName: "Buildings")
let results = (try? context.executeFetchRequest(request)) as? [Buildings] ?? []
with my class as:
class Buildings: NSManagedObject {
#NSManaged var name: String
convenience init(context: NSManagedObjectContext, name: String) {
let description = NSEntityDescription.entityForName("Buildings", inManagedObjectContext: context)!
self.init(entity: description, insertIntoManagedObjectContext: context)
self.name = name
}
}

You need to overwrite awakeFromFetch() method in your Buildings class like
func awakeFromFetch() {
super.awakeFromFetch()
// do your initialization
}
This method is called on every fetch of the object from the persistent store.

What you are talking about is faulting, basically Core Data tries to keep its memory footprint as low as possible and one of the strategies it uses to accomplish this is faulting. When you fetch the records for your entity, Core Data executed the fetch request, but it didn't fully initialize the managed objects representing the fetched records.
The moment you access an attribute or relationship of a managed object, the fault is fired, which means that Core Data changes the fault into a realized managed object.

Related

Swift Core Data - storing an array of custom types

I am trying to create a data model which mirrors a view model that I use to handle an API call, the idea being that I will be able to store all the necessary data in core data and then access it when the user is offline, effectively giving the app offline functionality.
However, there is one entity which I need to store which is an array of a custom class that I have in the app:
[OrderSheet]
This is a struct defined as follows:
struct OrderSheet {
let order: SheetClass // codable class
let sheet: Sheet // codable struct
init(fuelOrder: SheetClass, sheet: Sheet) {
self.order = order
self.sheet = sheet
}
}
How can I create an entity that would be capable of storing the above?
One simple way would be to have an entity that holds only one Data field (Binary Data in xcdatamodel settings), which would be the orderSheet itself.
Before going with this solution, I'd like to mention that, one down side of this approach is; if later in the future, any of the models inside OrderSheet changes, you won't be able to retrieve already stored objects as conversion will fail. One way of overcoming this issue would be declaring everything inside OrderSheet and sub models as Optional. But if it is not so crucial, meaning, if not being able to read old models on user's device after an app update is okay, (maybe they will be replaced with new networking call) then you can go with not marking properties as optional either.
Lets imagine you create an entity named OrderSheetManaged with one field as I mentioned like following:
import Foundation
import CoreData
#objc(Entity)
public class OrderSheetManaged: NSManagedObject {
}
extension OrderSheetManaged {
#nonobjc public class func fetchRequest() -> NSFetchRequest<OrderSheetManaged> {
return NSFetchRequest<OrderSheetManaged>(entityName: "OrderSheetManaged")
}
#NSManaged public var orderSheet: Data?
}
I will write some code for NSManagedObjectContext, which is not directly related to your question, you should make research on how to initialise a core data stack and a managed context from it if you are not familiar with that since it is crucial.
I also do some force unwrapping for simplicity, make sure to not force unwrap where not needed in production code.
Now whenever you have an actual OrderSheet object (it is orderSheet in my example below), that was parsed before, you are going to convert it to Data and persist it with new Core Data model as following:
// unrelated to question, it should already be initialised from core data stack, I just init with
// concurrency type to make compiler happy, dont do this before further research.
let yourManagedContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let entityDescription = NSEntityDescription.entity(forEntityName: "OrderSheetManaged",
in: yourManagedContext)
let dataForCoreData = try! JSONEncoder().encode(orderSheet)
let managedOrderSheet = NSManagedObject(entity: entityDescription!, insertInto: yourManagedContext)
managedOrderSheet.setValue(dataForCoreData, forKey: "orderSheet")
Now we have persisted your object as Data inside a wrapper core data model (OrderSheetManaged)
Let's see now how we can fetch these models from our core data and convert it back to OrderSheet model:
// when you fetch it
var orderSheets = [OrderSheet]()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "OrderSheetManaged")
var coreDataObjects: [NSManagedObject]!
do {
coreDataObjects = try yourManagedContext.fetch(request) as? [NSManagedObject]
for coreDataObject in coreDataObjects {
if let orderSheetData = coreDataObject.value(forKey: "orderSheet") as? Data {
let orderSheet = try! JSONDecoder().decode(OrderSheet.self, from: orderSheetData)
orderSheets.append(orderSheet)
}
}
} catch {
error
}
Now you will have all your stored order sheets inside orderSheets array.
You can also write some utility methods to easily modify core data models by converting orderSheet data inside of them to OrderSheet first and then again converting it back to Data after modifying and then persisting again with setValue.

Implementing Codable and NSManagedObject simultaneously in Swift

I have an order processing application I'm working on for my employers that was originally designed to get all data about orders, products, and clients dynamically from the API. So all of the objects and and all of the functions dealing with those objects are interacting in the app with a "pass by value" expectation, utilizing structs conforming to Codable.
I now have to cache pretty much all of these objects. Enter CoreData.
I have no desire to create two files for one object(one as a Codable struct and the other as an NSManagedObject class) and then trying to figure out how to convert one to another. So I want to implement both in the same file...while still somehow being able to use my "pass by value" code.
Perhaps this is impossible.
edit
I'm looking for something a bit simpler than rebuilding all my data structures from the ground up. I understand I'll have to do some alterations to make a Codable struct compatible with a NSManagedObject class. I'd like to avoid making a custom initializer that requires me to enter in every property by hand, because there's hundreds of them.
In the end, it sounds like there is no "good" solution when migrating from an API dynamic app without caching to a cached app.
I decided to just bite the bullet and try the method in this Question: How to use swift 4 Codable in Core Data?
EDIT:
I couldn't figure out how to make that work so I used the following solution:
import Foundation
import CoreData
/*
SomeItemData vs SomeItem:
The object with 'Data' appended to the name will always be the codable struct. The other will be the NSManagedObject class.
*/
struct OrderData: Codable, CodingKeyed, PropertyLoopable
{
typealias CodingKeys = CodableKeys.OrderData
let writer: String,
userID: String,
orderType: String,
shipping: ShippingAddressData
var items: [OrderedProductData]
let totals: PaymentTotalData,
discount: Float
init(json:[String:Any])
{
writer = json[CodingKeys.writer.rawValue] as! String
userID = json[CodingKeys.userID.rawValue] as! String
orderType = json[CodingKeys.orderType.rawValue] as! String
shipping = json[CodingKeys.shipping.rawValue] as! ShippingAddressData
items = json[CodingKeys.items.rawValue] as! [OrderedProductData]
totals = json[CodingKeys.totals.rawValue] as! PaymentTotalData
discount = json[CodingKeys.discount.rawValue] as! Float
}
}
extension Order: PropertyLoopable //this is the NSManagedObject. PropertyLoopable has a default implementation that uses Mirror to convert all the properties into a dictionary I can iterate through, which I can then pass directly to the JSON constructor above
{
convenience init(from codableObject: OrderData)
{
self.init(context: PersistenceManager.shared.context)
writer = codableObject.writer
userID = codableObject.userID
orderType = codableObject.orderType
shipping = ShippingAddress(from: codableObject.shipping)
items = []
for item in codableObject.items
{
self.addToItems(OrderedProduct(from: item))
}
totals = PaymentTotal(from: codableObject.totals)
discount = codableObject.discount
}
}

Abstracting Core Data Fetch Request

func fetchRequestFromViewContext(nameOfEntity: NSManagedObject) {
let fetchRequest = NSFetchRequest<nameOfEntity>(entityName: "\(nameOfEntity)")
do {
let result = try? CoreDataStack.instance.viewContext.fetch(fetchRequest)
}
}
Trying to abstract the core data fetch request therefore making an argument of type managed object and passing it into the fetch request generic but is not letting me, am I on the right track on abstracting this core data fetch request?
NSFetchRequest(entityName:) takes a String but nameofEntity was given as an NSManagedObject. Change that to a String and then also pass in the type of the entity. You can use generics (the <T> below) to allow for any class conforming to NSManagedObject.
func fetchRequestFromViewContext<T: NSManagedObject>(nameOfEntity: String, type: T.Type) {
let fetchRequest = NSFetchRequest<T>(entityName: nameOfEntity)
do {
let result = try? CoreDataStack.instance.viewContext.fetch(fetchRequest)
}
}
To call this you would simply do:
fetchRequestFromViewContext(nameOfEntity: "YourEntity", type: YourEntity.self)

Best practices with custom NSManagedObjects

I receive lots of messages as a part of app logic which I want to store to persistent storage , I am using core data for the same , I have created a NSManagedObject subclass which represents message entity in my model. I want to know what is the best approach to create the object and save it,
1. The object will be created and saved
2. The object will be retrieved, updated and saved.
I want to make sure I use same managed context while saving the object , currently app is facing random freezes or crashes, which I suspect are due to improper use of context. Below is how the object is created and saved.
class MessageFactory: NSObject {
static func createMessage(state:Int,type:MessageType) -> Message {
let appDelegate = AppDelegate.shared()
let context = appDelegate.persistentContainer.viewContext // persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Message", in: context)
let message = Message(entity: entity!, insertInto: context)
message.setup(state:state,type:type)
return message
}
}
class Message : NSManagedObject {
var currentState: MessageState?
#NSManaged var type : String
#NSManaged var duration : String
#NSManaged var mediaUrl : String
func save() -> Bool {
do {
try self.managedObjectContext?.save()
return true
}
catch {
return false
}
}
}
Also I am passing this object to few functions as well , modify them there and save .
When I am passing this object to closure I am passing its ID and retrieving object using ID inside closure and them processing it.
I want to know if my above method is correct , if not what changes are needed.

How do I initialize a new NSManagedObject and set a relationship to another NSManagedObject within a single managed object context?

I'm creating a new NSManagedObject and trying to establish a relationship to another NSManagedObject but I get this error:
Illegal attempt to establish a relationship 'lift' between objects in
different contexts
I know why this is happening - I just don't know how to fix it.
I'm calling this function to create the new object:
func createNewLiftEvent() -> LiftEvent {
let entity = NSEntityDescription.entityForName("LiftEvent", inManagedObjectContext: moc!)
let newLiftEvent = LiftEvent(entity: entity!, insertIntoManagedObjectContext: moc)
return newLiftEvent
}
My LiftEvent instance is setting its own values at initialization. In awakeFromInsert, three helper methods are called and they use a class called LiftEventDataManager (dataManager) which is of course what's creating the second managedObjectContext I don't want:
override func awakeFromInsert() {
super.awakeFromInsert()
let defaultUnit = getDefaultWeightUnit() // returns an NSManagedObject
self.weightUnit = defaultUnit
let defaultLift = getDefaultLift() // returns an NSManagedObject
self.lift = defaultLift
let defaultFormula = getDefaultFormula() // returns an NSManagedObject
self.formula = defaultFormula
self.maxAmount = 0.0
}
This is an example of one of those helper methods:
func getDefaultFormula() -> Formula {
let formulasArray = dataManager.fetchSelectableFormulas() // this is what creates a new managedObjectContext
let defaultFormula = NSUserDefaults.formula().rawValue
formula = formulasArray[defaultFormula]
return formula
}
The createNewLiftEvent() function is in the LiftEventDataManager class along with the methods like the .fetchSelectableFormulas() method called within the helper function above.
Apple's Core Data Programming guide doesn't say anything about this particular problem and I haven't found any threads on SO that address the problem of multiple context at initialization time.
How can I create the new LiftEventObject in 'context A' and fetch those other managed objects and get them into 'context A' so I can set the relationships?
One option could be to pass around your original LiftEvent object and have your helper methods do their thing using that LifeEvent's managedObjectContext (all managed objects have a reference to their context, so you can make use of that fact).
(May not apply, but FYI, be careful if threads come into play. You may need to also use performBlockAndWait)