How can document-based settings be stored? - swift

I have a Core Data, document-based, storyboard-backed, OS X project. Each document will have a specific calendar year and province/state it's operating in, so I made a Settings entity that asks for these values when the document is created. Since certain behaviours are based on these Settings, other custom NSManagedObject classes need to ask for them, and it doesn't seem right to execute an NSFetchRequest every time I need the value. However, I'm not sure where to copy the values to, outside Core Data.
AppDelegate - as far as I'm aware, there's only one per application, not per document, so this won't be appropriate.
Document - this seems best, but I'm not sure how to find the document again later, from inside an NSManagedObject. I tried defining a var year inside Document, then from the NSManagedObject calling theWindow.controller?.document?.year, but theWindow is nil if I don't set it myself. Also, theWindow is global, outside AppDelegate, so even if I do define it (in Document's makeWindowControllers() by theWindow = windowController.window), it again becomes per-app, so every time I call this line, I'm finding the year of the last-opened document (the last one to overwrite theWindow), not the active document. I'm not sure how to find a reference to the current window.
User Defaults - again, this is per-app, not per-document. My current solution uses these defaults, then redefines the defaults (with an NSFetchRequest) every time windowDidBecomeMain is called, so the current Document sets the User Defaults to its own Settings.
It seems unlikely I'm the only one to want settings per-document, but I can't seem to find any examples or previous questions on how/where to define these. Any help is greatly appreciated!

You're describing per-document metadata, and fortunately Core Data supports this kind of metadata. Each persistent store file (which is what NSPersistentDocument uses) can have its own metadata dictionary with whatever keys and values you need. It's kind of like user defaults except that it's part of the document. This data is part of the file but separate from the SQLite store that makes up the actual document data.
The metadata API is on NSPersistentStoreCoordinator. You get your coordinator in a document via self.managedObjectContext.persistentStoreCoordinator. There are a few methods there to read and write the document metadata. Save your year and province/state in the metadata, and then look it up and consult it when opening a document.
Core Data has its own metadata that you don't want to lose-- so when editing metadata, make sure to look up the existing metadata, modify it, and save the new data. Don't just assign a completely new metadata dictionary to the document.

Related

Detect and handle certain conflicts in Core Data with iCloud Sync

I'm trying to create a note-taking like app that uses NSPersistentCloudKitContainer and core data.
The store uses the NSMergeByPropertyObjectTrumpMergePolicy, which is fine for almost every property. For example, if the name of a file is changed on two different devices, then it's fine to use the latest value.
The problem is that the note text cannot be overridden by the latest value if it's changed on two devices at once. It needs to be detected as a conflict so the user can choose which version they want to keep.
I can replicate the behavior by turning off wifi on one device and writing content, then writing content on a different device at the same time. When I turn the wifi back on, whichever device saved the changes last completely overrides the other device's text.
What I'd like to accomplish is detect when there is a conflict of text, then create a duplicate file called "Conflicted Copy." Bonus points if someone can tell me how Apple Notes magically merges text without ever creating a conflict. I really only need a simple solution though that prevents data loss.
Any help in the right direction would be appreciated!
The conflict arises when CoreData & CloudKit sync an object to the persistent store while a managed context has an updated version of the object that has not yet been saved.
Any merge policy, including a custom merge policy, is used to create a single object that will be stored in the persistent store, but you want to have both conflicting objects so that the user can choose one of them.
Thus automatic conflict resolution, including with a custom merge policy, cannot be applied.
Instead, use the default merge policy of type error. The docs say
If a save fails because of conflicting objects, you can find the IDs
of those objects in error’s userInfo dictionary. Use the
NSInsertedObjectsKey and NSUpdatedObjectsKey keys to extract the
object IDs.
This means, you can keep the properties of the not-yet-stored conflicting object in the managed context, and re-fetch the properties of the conflicting object from the persistent store. This will overwrite the conflicting in-context version and "resolve" the conflict, but you still have now the conflicting properties and can present them to the user.
If the user selects the version in the persistent store, you are done. Otherwise update the objects properties with the kept values as required and save the context.
PS: Here is for your information how to implement a custom merge policy, although it cannot be applied here, just because such info is difficult to find.
EDIT:
I can imagine 2 ways to have one merge policy for your "normal" objects, and the error policy for the text objects:
A merge policy is set for a managed context. So you could have one context with NSMergeByPropertyObjectTrumpMergePolicy, and another one with NSErrorMergePolicy. So if it is feasible to fetch most objects into the 1st context, but the text storing objects into the 2nd one, you could apply both conflict resolution strategies.
During conflict resolution, i.e. in func resolve(optimisticLockingConflicts…, one has to call super.resolve(optimisticLockingConflicts: (see my example implementation cited above). So if you set NSMergeByPropertyObjectTrumpMergePolicy to your context, but call super only for your "normal" objects, this merge policy would not be applied to your text objects, and you could handle the conflict by your own. Warning: As far as I know this is non-standard, and I am not sure what happens if you don't call super.

Mongo DB Collection Versioning

Are there any best practices or ways we can use to version a collection or objects in a collection in Mongo DB?
The requirement of versioning a collection is because, the objects in the collection maybe added with new attributes going forward but the already added objects (i.e. old objects) will not be having these attributes and the values for these new attributes. So on retrieval, we need to make sure, the code is not broken in de-serializing the different versions of the same object in the collection.
I can think of adding a version attribute to the objects explicitly, but are there any better built in alternatives in Mongo DB for handling this versioning of objects and/or collections.
Thanks,
Bathiya
I guess the best approach would be to update all objects in a batch process when you start using the new software on the server, since otherwise you’ll never know when an object will be updated and you’ll need to keep the old versions of those forever around.
Another thing what I’m doing so far, and it worked (so far), to have the policy to only allow adding new properties to the object. This way in worst case the DB won’t have all the data, but that is fine with all the json-serializers I know. But this means you aren’t allowed to delete or rename properties as well as modifying their type (from scalar value to object, array; from object to scalar, array; …).
Since usually I want to store additional information instead of less, this seems like a good solution without any real limitation for me.
If you need to change the type because scalar value isn’t enough, you can still create some code around which would transform it for every object which has the old value into the new one. And if a bulk update from your side is able to perform the changes I’d still do it but sometimes it needs user input.
For instance if you used to save passwords only as md5-hash it was a scalar value. But someone told you, they should be stored as sha512 with a salt together, now you need an object field for the password, you could call it password_sha512 where you store the salt and the hashed password.

How can I detect when a NSPersistentStore needs to be saved?

I have an iOS application where I use coreData to store my "documents". They all share a common NSManagedObjectContext, and I frequently save the context.
I would like to keep track of the last modification date for the various "documents" (where each one is a separate NSPersistentStore) and store the date on a particular unique "root" object that each store has.
I could try to keep the modification time stamp up to date while the document is being modified, but it would be cleaner and more robust if I could just find out which persistent stores need saving at the time I am saving the context.
I can't find any way to detect if a persistent store needs saving. I can query the NSManagedObjectContext to see which managed objects need saving, although I can't find an easy way to see which store an object belongs to.
It seems like this is not such a strange thing to do and core data has all of the information that I am looking for, but I am having trouble finding an easy way to get access to that data.
Does anyone know of an easy way?
If I can't find an easier way, I will simply loop over the deleted / modified / inserted objects from the context, and write special code for each entity type to determine the store that the object belongs to.
Thanks in advance for any help!
Ron
[[managedObject objectID] persistentStore] is the persistent store you're looking for (or possibly nil if the object has not been saved yet).
The documentation suggests that it's nil if you've assigned it to a store but haven't saved; I'm not sure that this is true (and I don't see anywhere else where this info might be saved). I'd check it behaviour on 3.x, 4.x, and 5.0 beta if you have access to it.

How to handle cleanup of external data when deleting Core Data objects

I am fairly new to Core Data and have run into an issue which others must have encountered.
My data model includes images and I am keeping those external to the database and simply storing a path/URL to the image. (as recommended in one of Apple's Core Data presentations)
When deleting my image object I can manually traverse relationships and remove the image files but I am wondering if there is a more elegant way to do this.
The ideal solution would be tied to the image object somehow and would work with Core Data undo/redo.
In your "image" entity class, implement willSave. Check [self isDeleted] and delete the file if so. This postpones actual deletion of the file until the store is saved, which gives you some undo-ability. Set up appropriate cascade rules to delete the image entities when their owner goes away, and there you go.
[eta.: Phil Calvin's comment below is right - didSave is probably a better place, if you're using multiple contexts.]
[eta. much later:] MartinW raises an excellent point - if the object has never been saved, will/did save won't get called. Deleting an un-saved object just un-inserts it from the context and throws it away, with no special lifecycle events. And just to complicate matters more, you can "undo" and "redo" a deletion, including (I think) this kind.
A few approaches that come to mind:
This might be a case for overriding prepareForDeletion:, plus awakeFromSnapshotEvents: to catch un-deletion and re-deletion. To support undo/redo, you'll need to not simply delete the file on the spot, but use some kind of "to be removed" registry (e.g. a shared mutable set of filenames to clean up when a save notification is posted). Then will/didSave are out of the picture.
Or, if you can live with BLOB fields instead of files, you could check the "allows external storage" box on a binary property, put the jpeg data there, and get some (not all) of the advantages of file storage without (most of) the headaches. Little binary will be kept in the db; anything bigger than a private threshold will be shunted out into a separate hidden core-data-managed file. Core data still has to load the whole thing into an NSData when faulting in the object, though. I use this approach for "user avatar"-type small images.
Finally, if nothing else is kept in the images directory, you could register for didSave notifications and manually clean up after any save. Run a fetch request for all the Image.filename properties, compare it to a directory listing of the images dir in question, delete as appropriate. I use this approach as my "big stick" during development to make sure everything else is doing what it should.
[Let me know of successes or hardships with these approaches and I'll keep this up to date.]
In your Image class, implement -didSave. In this method, check whether [self isDeleted] and if it's YES, delete the image files from disk.
It's important to do this in -didSave rather than -willSave particularly if you have multiple managed object contexts associated with your persistent store. willSave is sent before Core Data discovers and reports (as a save error) any merge conflicts. This means you could potentially receive willSave either:
Multiple times if you implement a merge strategy that merges objects and re-tries the save
Before a save that never commits to the persistent store due to a merge conflict
Deleting the image files prematurely could cause a crash later when those images are accessed from another managed object context.
I would suggest overriding the image entities prepareForDeletion method to delete the image file on disk. The method will only be called with the image entity object is actually deleted.
The best practice would be (which I think would be the same to what #Rog implied) to have an entity for stored images, and make your objects have a relationship to that entity, rather than storing paths in each object. In that case, you can just find one object which represents the picture to be deleted and delete it, than the reverse relationship can be automatically nullified.

Property Lists: How to use it to provide easy exchangeable default data to the user?

I want to ship some default data with my app. This data can be stored perfectly in a property list. The strucure is simple:
Root
0
animalType = cat
animalName = Tom
1
animalType = dog
animalName = Rambo
I thought: When I use a property list rather than hard-coding it somewhere, then I could easily provide more defaults to choose from after the app is distributed already. If I would hard-code it, I would have to provide heavy upgrades every time, and Apple would take weeks to let them pass.
But there's one thing I don't get. I make that property list manually in Xcode and put that in my Resources group. As far as I know, Xcode would compile it into some binary format. In my app I would use NSPropertyListSerialization to create a NSDictionary out of it. But this property list would not get placed into the documents directory of the sandbox, right? So if the application would download an update of that property list some time in the future, the update would have to go into documents dir, but the app would still look at the old plist in the root, right? Where to put it? Must I copy it to documents, just like an sqlite database?
And the other thing: When I edit the plist and provide the whole thing as an XML for download/update from a server, then of course that thing would not be "compiled" into some binary format. How would that work? Would NSPropertyListSerialization have trouble reading it? Must I compile that thing every time with XCode and let the app download some binary stuff?
There are two commonly used property list formats: proprientary binary format and xml format (DTD). You can use either of them, and NSPropertyListSerialization will detect automatically, which one is used for your data when de-seralizing.
XML format is more verbose, but it's simple to generate. If you're publishing data from server, you might consider generating xml plist, and compress it with gzip or something.
Now to your first question about where to store the data. To make application payload smaller you might first check documents directory for updated plist, and if it is not present - load default plist from your application bundle.
One general approach used is to always copy plists or other updated elements into the application documents directory - then you just always load from there, and replace when there is an update.
Or you could pre-load the data into a database, download plist updates and refresh the database entries at that time.