Swift Core Data - storing an array of custom types - swift

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.

Related

SwiftUI FileDocument using Class instead of Struct

I've started a document-base macOS app in SwiftUI and am using a FileDocument (not a Reference FileDocument) as the type of document. In every tutorial I've seen, even in Apple's own WWDC video discussing it (https://developer.apple.com/wwdc20/10039), a struct is always used to define the FileDocument.
My question is: is there an issue with using a class in the struct defining the document. Doing so doesn't result in any Xcode warnings but I wanted to be sure I'm not creating any issues for my app before going down this path.
Below is some example code for what I'm talking about: declaring TestProjectData as a class for use within the DocumentDataAsClassInsteadOfStructDocument - struct as a FileDocument?
public class TestProjectData: Codable{
var anotherString: String
init(){
anotherString = "Hello world!"
}
}
struct DocumentDataAsClassInsteadOfStructDocument: FileDocument, Codable {
var project: TestProjectData
init() {
project = TestProjectData()
}
static var readableContentTypes: [UTType] { [.exampleText] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let _ = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
let fileContents = try JSONDecoder().decode(Self.self, from: data)
self = fileContents
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = try JSONEncoder().encode(self)
return .init(regularFileWithContents: data)
}
}
It appears that yes, we need to use a struct for documents. See this post for a thorough example of the issues you can run into if you use a class instead of a struct.
SwiftUI View doesn't update
SwiftUI's architecture is all about using value types for speed and consistency. E.g. on a state change we create all the View structs and then SwiftUI diffs and uses the result to init/update/deinit UIView objects.
I believe the same thing happens with FileDocument. The struct is diffed on a change and the difference is used to init/update/deinit a UIDocument object.
If you init object vars inside these structs then basically it is a memory leak because a new object will be init every time the struct is created which is every time something changes. Also chances are you'll end up using the wrong instance of the object because there will be so many. You can see this type of problem surface when blocks are used inside body, the callback usually happens on an older version of the View struct, which isn't a problem when everything is value types but it is a big problem if referencing old objects.
Try to stick to value types in SwiftUI if you can, if you use objects you'll run into all kinds of headaches. And I don't think ReferenceFileDocument even works yet - I seem to remember it needs some kind of undo manager workaround.

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
}
}

How to reduce mutability with nested objects stored in Realm?

Full code on github
I am trying to rewrite my app to reduce mutability and take advantage of functional programming. I am having trouble figuring out where to start, since it seems like my architecture is to use modification in place almost everywhere. I could use some advice on a simple starting point of how to break this down into smaller pieces where I am maintaining immutability at each modification. Should I change my data storage architecture so that I am only storing/modifying/deleting the leaf objects?
Right now, from the root ViewController, I load my one monster object ExerciseProgram (which contains a RealmList of Exercise objects, which contains a RealmList of Workouts, which contains a RealmList of Sets....)
final class ExerciseProgram: Object {
dynamic var name: String = ""
dynamic var startDate = NSDate()
dynamic var userProfile: User?
var program = List<Exercise>()
var count: Int {
return program.count
}
}
Loaded here one time in MasterTableViewController.swift:
func load() -> ExerciseProgram? {
let realm = try! Realm()
return realm.objects(ExerciseProgram).first
}
and then modify the single ExerciseProgram object in place throughout the app, such as when recording a new workout.
To create a new Workout, I instantiate a new Workout object in RecordWorkoutTableViewController.swift:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if doneButton === sender {
if let date = newDate, weight = newWeight, setOne = newSetOne, setTwo = newSetTwo {
let newSets = List<WorkSet>()
newSets.append(WorkSet(weight: weight, repCount: setOne))
newSets.append(WorkSet(weight: weight, repCount: setTwo))
newWorkout = Workout(date: date, sets: newSets)
}
}
}
Which unwinds to ExerciseDetailTableViewController.swift where the storage occurs into the same monster ExerciseProgram object retrieved at the beginning:
#IBAction func unwindToExerciseDetail(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? RecordWorkoutTableViewController, newWorkout = sourceViewController.newWorkout {
let realm = try! Realm()
try! realm.write {
exercise.recordWorkout(newWorkout)
}
}
}
This behavior is replicated all over my app. If I want to edit or delete an existing workout, it's exactly the same.
The Exercise class is just this:
final class Exercise: Object {
dynamic var name = ""
dynamic var notes: String?
var workoutDiary = List<Workout>()
dynamic var goal = 0
...
func recordWorkout(newWorkout: Workout) {
workoutDiary.append(newWorkout)
}
func replaceWorkout(originalWorkout: Workout, newWorkout: Workout) {
workoutDiary[workoutDiary.indexOf(originalWorkout)!] = newWorkout
}
}
From what I can tell, looking at that schema, no, you shouldn't change it. If it's representing the types of information and their relations properly and it's already working in your app, then there's no need to change it.
If you feel it is overly complex or confusing, then it may be necessary to go back and look at your data model design itself before actually doing more work on the code itself. Review each relationship and each property in the linked objects, and make sure that it's absolutely critical that the data is saved at that level. In any case, Realm itself is very good at handling relationships between objects, so it's not 'wrong' to have several layers of nested objects.
Either way, Realm itself lends itself pretty well to functional programming since every property is explicitly immutable out of the box. Functional programming doesn't mean everything has to be immutable always though. Inevitably, you'll have to reach a point where you'll need to save changes to Realm; the mindset behind it is that you're not transforming data as you're working on it, and you minimise the number of points that actually do so.

Dynamically mirror properties to another class

I'm currently working on using Realm together with the Swift framework Oatmeal. Now, both of those require model classes (Oatmeal can do automatic serialisation). My structure for a given Oatmeal model and Realm model are as follows:
class OatmealModel: SerializableObject {
var id: Int = 0
var someOtherProperty: String?
var anotherUnknownProperty: SomeType?
var store: RealmModel?
}
class RealmModel: Object {
dynamic var id: Int = 0
dynamic var someOtherProperty: String?
dynamic var anotherUnknownProperty: SomeType?
override static func primaryKey() -> String? {
return "id"
}
}
As should be clear, they both hold the same properties, but the OatmealModel is always the one I interact with. Aside from this, the OatmealModel also implements a protocol to retrieve data:
protocol PersistentStore {
func save()
func findStore()
func all() -> [PersistentStore]
func filter(predicate: NSPredicate) -> [PersistentStore]
}
These methods allow us to fetch from the Realm database and then hydrate the oatmeal model, and get only oatmeal models back. And then calling save will write back to realm.
So essentially, what I want to do is:
When retrieving entities from the Realm DB and hydrating Oatmeal models, this should be done automatically. Currently, every oatmeal model has to set the all and filter (and save) methods separately just to replace the class name, and the properties currently have to be set manually. My plan is to somehow use reflection on both the oatmeal model and the realm model to see which properties have the same name and type, and then write them from one to the other.
Currently, I have no idea how to go about this, so any hints on how I could use reflection for this would be helpful. I assume somehow use the Mirror to extract the properties and then loop through them and see if one of the same name exists in the other?
Also, to achieve this in the end, since it'll be the models doing this to return instances of itself, is it somehow possible for a model to create an array of its own type, without explicitly writing the type? E.g [Self] (which doesn't work) rather than [OatmealModel]?
I'm fairly new to Swift, but am familiar with a few other languages and concepts, any help is greatly appreciated!

Fetching from CoreData and casting

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.