FileDocument with UIManagedDocument/core data - core-data

When using SwiftUI to create a document based app, the default document type is to subclass FileDocument.
All examples lead to simple value types to be used in this document type.
I'm looking to create a UIManagedDocument in SwiftUI but there doesn't seem to be any mention of using FileDocument with core data. I noticed a ReferenceFileDocument but this leads to no examples either...
Has anyone had any experience of using either SwiftUI document type for core data based documents?

After some more months, I came across this question once again.
Since my last comment on September 18th, I've worked myself on solving the puzzle of building a SwiftUI document-based app using Core Data.
Looking more in-depth I learned that the UIManagedDocument (respectively its parent UIDocument) infrastructure is really close/similar to what SwiftUI tries to implement. SwiftUI even uses UIDocument in the background to do "its magic". UIDocument and UIManagedDocument are simply some more archaic remnants of times where Objective-C was the dominant language. There was no Swift and there were no value-types.
In general I can give you the following tips to solve your challenge using Core Data within a UIManagedDocument:
first of all, if you want to use Core Data, you will have to use a package/bundle based document format. This means your UTType will have to conform to .package (=com.apple.package in your Info.plist file). You won't be able to make Core Data work with only a plain file document.
extension UTType {
static var exampleDocument: UTType {
UTType(exportedAs: "com.example.mydocument", conformingTo: .package)
}
}
use a ReferenceFileDocument based class to build a wrapper document for your UIManagedDocument. This is necessary because you will have to know about the time when the object is released. In deinit you will have to call managedDocument.close() to ensure the UIManagedDocument is properly closed and no data is lost.
the required function init(configuration:) is going to be called when an existing document is opened. Sadly, it is of no use when working with UIManagedDocument because we have only access to the FileWrapper of the document and not the URL. But the URL is what you need to initialize the UIManagedDocument.
the required function snapshot(contentType:) and fileWrappper(snapshot:, configuration:) is only used to create a new empty document. (This is because we won't use the SwiftUI integrated UndoManager but the one from UIManagedDocument respectively Core Datas NSManagedObjectContext.) Therefore it is not relevant what your type for Snapshot is. You can use a Date or Int because the snapshot taken with the first function is not what you are going to write in the second function.
The fileWrappper(snapshot:, configuration:) function should return the file structure of an empty UIManagedDocument. This means, it should contain a directory StoreContent and an empty file with the filename of the persistent store (default is persistentStore) as in the screenshot below.
The persistentStore-shm and persistentStore-wal files are going to be created automatically when Core Data is starting up, so we do not have to create them in advance.
I am using the following expression to create the FileWrapper representing the document: (MyManagedDocument is my UIManagedDocument subclass)
FileWrapper(directoryWithFileWrappers: [
"StoreContent" : FileWrapper(directoryWithFileWrappers: [
MyManagedDocument.persistentStoreName : FileWrapper(regularFileWithContents: Data())
])
])
above steps allow us to create an empty document. But it still cannot be connected to our UIManagedDocument subclass, because we have no idea where the document (represented by the FileWrapper we have created) is located. Luckily SwiftUI is passing us the URL of the currently opened document in the ReferenceFileDocumentConfiguration which is accessible in the DocumentGroup content closure. The property fileURL can then be used to finally create and open our UIManagedDocument instance from the wrapper. I'm doing this as follows: (file.document is an instance of our ReferenceFileDocument class)
DocumentGroup(newDocument: { DemoDocument() }) { file in
ContentView()
.onAppear {
if let url = file.fileURL {
file.document.open(fileURL: url)
}
}
}
in my open(fileURL:) method, I then instantiate the UIManagedDocument subclass and call open to properly initialize it.
With above steps you will be able to display your document and access its managedObjectContext in a view similar to this: (DocumentView is a regular SwiftUI view using for example #FetchRequest to query data)
struct ContentView: View {
#EnvironmentObject var document: DemoDocument
var body: some View {
if let managedDocument = document.managedDocument {
DocumentView()
.environment(\.managedObjectContext, managedDocument.managedObjectContext)
} else {
ProgressView("Loading")
}
}
}
But you will soon encounter some issues/crashes:
You see the app freeze when a document is opened. It seems as if UIManagedDocument open or close won't finish/return.
This is due to some deadlock. (You might remember, that I initially told you that SwiftUI is using UIDocument behind the scene? This is probably the cause of the deadlock: we are running already some open while we try to execute another open command.
Workaround: run all calls to open and close on a background queue.
Your app crashes when you try to open another document after having previously closed one. You might see errors as:
warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Item' so +entity is unable to disambiguate.
warning: 'Item' (0x6000023c4420) from NSManagedObjectModel (0x600003781220) claims 'Item'.
warning: 'Item' (0x6000023ecb00) from NSManagedObjectModel (0x600003787930) claims 'Item'.
error: +[Item entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
After having debugged this for some hours, I learned that the NSManagedObjectModel (instantiated by our UIManagedDocument) is not released between closing a document and opening another. (Which, by good reason, is not necessary as we would use the same model anyway for the next file we open). The solution I found to this problem was to override the managedObjectModel variable for my UIManagedDocument subclass and return the NSManagedObjectModel which I'm loading "manually" from my apps bundle. I suppose there are nicer ways to do this, but here is the code I'm using:
class MyManagedDocument: UIManagedDocument {
// We fetch the ManagedObjectModel only once and cache it statically
private static let managedObjectModel: NSManagedObjectModel = {
guard let url = Bundle(for: MyManagedDocument.self).url(forResource: "Model", withExtension: "momd") else {
fatalError("Model.xcdatamodeld not found in bundle")
}
guard let mom = NSManagedObjectModel(contentsOf: url) else {
fatalError("Model.xcdatamodeld not load from bundle")
}
return mom
}()
// Make sure to use always the same instance of the model, otherwise we get crashes when opening another document
override var managedObjectModel: NSManagedObjectModel {
Self.managedObjectModel
}
}
So this answer has become really lengthy, but I hope it is helpful to others struggling with this topic. I've put up this gist with my working example to copy and explore.

Related

Best practice core data same view for creating and editing [duplicate]

This question already has answers here:
swiftui how to fetch core data values from Detail to Edit views
(2 answers)
Closed 11 months ago.
I try to use core data to store persistent data on an iOS device. I've got user flows to create and edit domain objects with a few related and deeply nested objects.
Those user flows are very similar, so I would like to use the same views for those tasks, just deciding on appear if the view got an existing domain object passed or it needs to create a new one.
After testing different approaches, nothing seems to fit this context so I wonder if there are recommended ways for this situation?
The following options got tested:
initializing the core data object in init() results in an Modifying state during view update, this will cause undefined behavior. warning
initializing the core data object in .onAppear requires the #ObservedObject var domainObjectPassed: DomainObject to be optional, not quite what I'm looking for as well
Any suggestions?
Already did that. I extracted the same logic into one view and have two distinct wrapper views that should handle this problem. But I've got the same situation one level higher.
struct CreateView: View {
#ObservedObject private var domainObject: DomainObject
init(moc: NSManagedObjectContext) {
domainObject = DomainObject(context: moc)
domainObject.id = UUID()
try? moc.save()
}
var body: some View {
CustomizeView(domainObject: domainObject)
}
}
-> results in warning from the first option
struct CreateView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject private var domainObject: DomainObject? = nil
var body: some View {
CustomizeView(domainObject: domainObject)
.onAppear {
...
}
}
}
-> requires domainObject in CustomizeView to be optional, not what I'm looking for
I am using the same concept (one view for editing and creating), but in my case I find myself comfortable using an optional to get the core data object. If you can live with an optional, this could work.
In this way, you just need to check if the object passed is nil: if it is, create the object, otherwise use the object passed.
In a very schematic way, the view could be:
struct EditOrCreate: View {
var coreDataObject: MyCoreDataEntity
// Optional object at initializer
init(objectPassed: MyCoreDataEntity? = nil) {
if objectPassed == nil {
coreDataObject = MyCoreDataEntity(context: thePersistentContainerContext)
} else {
coreDataObject = objectPassed!
}
}
var body: some View {
// All the text fields and save
}
}
When creating, just call EditOrCreate().
When editing, just pass the object: EditOrCreate(objectPassed: theObjectBeingShown).
You can also make coreDataObject an optional variable (which is what I did in my code), depending on how you want to handle your logic - for example, to cancel the creation before saving: in that case, you need to check for nil after the user has confirmed the creation of the new object.
Some programmers believe that "Premature Optimization Is the Root of All Evil". What I've seen happens when creating generic view code with only a small number of cases that have not yet been thought through is that after creating the generic version, you realise that you need to customise one of them, so end up trying to override the default behaviour in certain cases, which gets real messy and you would have been better off just with separate versions in the first place.
In SwiftUI, the View structs are lightweight and there is no issue with creating lots of them. Try to break your View structs up to be as small as possible then you can compose them together to form your different use cases.
The time you save could be spent on learning more about how SwiftUI works and fixing the issues in the code you posted. E.g. we don't init objects in the View struct init, or in body. Those need to run fast because that code runs quite frequently and creating objects is a comparably heavy task, also those objects that are created are immediately discarded after SwiftUI has finished building the View struct hierarchy, diffed it from last time, and updated the actual UIKit Views on screen. I highly recommend watching every WWDC SwiftUI video, there is a lot to learn and there is a lot of magic going on under the hood.
Another thing you could spend time learning is Swift generics and protocols. These are powerful ways to build reusable code using value types instead of class inheritance that we would typically use as ObjC/UIKit developers which tends to be buggy. You can read more about here: Choosing Between Structures and Classes (Apple Developer)

SwiftUI .fullScreenCover modifier causes weird behavior of core data object creation

After incorporating core data into my app, I've got a view that I use for editing a specific type of domain object. Because creating and editing is very similar, I use this view for both use cases.
If I pass an already existing object, everything works fine. But if I have to create a new object which I'm able to pass, the following happens in the background:
As you can see, when the .fullScreenCover gets triggered, it's not creating a single core data object, it's creating a random number of objects.
The code is this:
func createEmptySession() -> DomainSession {
let session = DomainSession(context: moc)
session.id = UUID()
return session
}
.fullScreenCover(isPresented: $showEditSessionViewModal){
NavigationView {
EditSessionView(showEditSessionViewModal: $showEditSessionViewModal, session: createEmptySession())
}
}
I think the problem is, that right now I'm creating an object directly in the init of the full screen view. The solution would be to create an object before that and pass this one, but I don't know how to do that.
Any suggestions?

NSFetchedResultsController + UICollectionViewDiffableDataSource + CoreData - How to diff on the entire object?

I'm trying to use some of the new diffing classes built into iOS 13 along with Core Data. The problem I am running into is that controllerdidChangeContentWith doesn't work as expected. It passes me a snapshot reference, which is a reference to a
NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>
meaning I get a list of sections/Object ID's that have changed.
This part works wonderfully. But the problem comes when you get to the diffing in the collection view. In the WWDC video they happily call
dataSource.apply(snapshot, animatingDifferences: true)
and everything works magically, but that is not the case in the actual API.
In my initial attempt, I tried this:
resolvedSnapshot.appendItems(snapshot.itemIdentifiersInSection(withIdentifier: section).map {
controller.managedObjectContext.object(with: $0 as! NSManagedObjectID) as! Activity
}, toSection: .all)
And this works for populating the cells, but if data is changed on a cell (IE. the cell title) the specific cell is never reloaded. I took a look at the snapshot and it appears the issue is simply that I have references to these activity objects, so they are both getting updated simultaneously (Meaning the activity in the old snapshot is equivalent to the one in the new snapshot, so the hashes are equal.)
My current solution is using a struct that contains all my Activity class variables, but that disconnects it from CoreData. So my data source became:
var dataSource: UICollectionViewDiffableDataSource<Section, ActivityStruct>
That way the snapshot actually gets two different values, because it has two different objects to compare. This works, but it seems far from elegant, is this how we were meant to use this? Or is it just in a broken state right now? The WWDC video seems to imply it shouldn't require all this extra boilerplate.
I ran into the same issue and I think I figured out what works:
There are two classes: UICollectionViewDiffableDataSource and UICollectionViewDiffableDataSourceReference
From what I can tell, when you use the first, you're taking ownership as the "Source of Truth" so you create an object that acts as the data source. When you use the second (the data source reference), you defer the "Source of Truth" to another data source (in this case, CoreData).
You would instantiate a ...DataSourceReference essentially the same way as a ...DataSource:
dataSourceReference = UICollectionViewDiffableDataSourceReference(collectionView: collectionView, cellProvider: { (collectionView, indexPath, object) -> UICollectionViewCell? in
let identifier = <#cell identifier#>
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
<#cell configuration#>
return cell
})
And then later when you implement the NSFetchedResultsControllerDelegate, you can use the following method:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)
{
dataSourceReference.applySnapshot(snapshot, animatingDifferences: true)
}
I watched the WWDC video as well and didn't see this referenced. Had to make a few mistakes to get here. I hope it works for you!

Enable saving of document NSManagedObjectContext immediately?

Starting from the standard Xcode Document-based Application w/ CoreData template on 10.7, I'm experiencing some frustrating behavior. I'm sure it's something simple that I'm overlooking.
Let's say in my NSPersistentDocument subclass, I have something like this, hooked up to a button in the window:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
[self.managedObjectContext save: NULL];
}
If I create a new document and click that button, I'll get the following error: This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation. I get this. We haven't saved yet, there are no persistent stores. Makes sense.
Now let's say I split this out into two actions, hooked up to different buttons, like so:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
}
- (IBAction)doOtherStuff:(id)sender
{
[self.managedObjectContext save: NULL];
}
If I create a new document and press the first button, then at some indeterminate time after pressing that button (dirtying the document), autosave will come along and autosave the document, which will create a store in the temp location. If I then press the second button, there are no complaints (since there's now a store.)
I need my document to be able to do managedObjectContext saves from the get-go. I'm kicking off some stuff on a background thread, and I need the background context's save operation (and notification) in order to merge the changes made by the background thread into the main thread's managedObjectContext.
I thought about trying to force an autosave, but the autosave process appears completely asynchronous, so I'd have to jump through hoops to disable any UI interaction that could cause a managedObjectContext save until the first autosave operation completes.
I also considered creating an in-memory store to bridge the gap between creating a new document and the first autosave, but then it's not clear to me how I would migrate that in memory store to the disk store and remove the memory store synchronously with the first autosave operation.
Anyone have any ideas on how I might handle this?
So I fooled around with this for a while, including trying #Aderstedt's suggestion. That approach didn't work, because faking a notification appears to merely tell the receiving context "hey, check with the persistent stores, I've updated them!", when in reality, I haven't, because there are none. I eventually found an approach that worked. Unfortunately it relies on Lion-only features, so I'm still looking for a way to do this that doesn't require Lion.
Background
I wanted to work with the NSPersistentDocument approach. Although I've not found this documented explicitly anywhere, I found several forum posts, and experienced a bunch of empirical evidence that you cannot call -[NSManagedObjectContext save:] on a context that belongs to an NSPersistentDocument. As mentioned in the question, if you call that before the document has ever been saved, it'll have no stores, so the save will fail. Even after the store exists, by saving the context directly (and not via the document save API) you are effectively changing the on-disk representation behind the NSPersistentDocument's back, and you will get the document popping sheets that say:
File has been modified by another application
In short, the NSPersistentDocument expects to control the saving action of the associated NSManagedObjectContext itself.
Also worth mentioning up front: the goal here was to make sure that the context used by the UI would trigger no (or at least minimal) I/O in order to remain responsive. The pattern I eventually settled on was to have 3 contexts. One context owned by the NSPersistentDocument, which would be responsible for doing file I/O in conjunction with the document. A second, effectively read-only, context for binding the UI to. (I realize that many folks want UI that mutates the model, so this may be less thrilling for them, but it wasn't a requirement for me.) And a third context for use on a background thread that asynchronously loads data from a web service, and hopefully pushes it into the other contexts so that it can be both saved on disk and presented in the UI without potentially blocking the UI on network I/O.
Lion-only Solution
The new parent/child NSManagedObjectContext feature in Lion's CoreData implementation is perfect for this. I replaced the NSPersistentDocument's NSManagedObjectContext with an new MOC of concurrency type NSPrivateQueueConcurrencyType. This will be the "root" context. Then I made the UI context with NSMainQueueConcurrencyType concurrency, and made it a child of the root context. Lastly I made the network-loading context a NSPrivateQueueConcurrencyType context which is a child of the UI context. The way this works is that we kick off a network load operation in the background, it updates the network context. When it's done, it saves the context. With parent/child relationships, saving a child context pushes the changes up into the parent context (the UI context) but does not save the parent context to the store. In my case, I also listen for the NSManagedObjectContextDidSaveNotification notification from the network context and then tell it's parent to save as well (which will push the changes from the UI context into the root/disk context, but will not save it to disk.)
At the end of this chain of events all the contexts are consistent, and we still haven't forced a real save of the underlying root context, and so we haven't run afoul of the NSPersistentDocument in its role of managing the on-disk representation.
One catch is that if you want to prevent saves of child contexts from generating undo (i.e. this was a network loading operation, there's nothing to undo) you have to disableUndoRegistration on each parent context as you propagate the changes up the chain.
Pre-Lion Efforts
I would really have liked to find a pre-Lion-compatible solution for this issue. I tried a few things before giving up. I first tried associating an in-memory store with the PSC on document init, so that I could do NSManagedObjectContext saves before the document saved, then migrate the in-memory store on the first save. That part worked great. But once an on-disk store existed, this approach was bogus because after it's saved to disk we have the same problem where any saving of MOCs connected to PSCs owned by an NSPersistentDocument have to be done by the document.
I also tried hacking up a mechanism to move changes from one context to another using the NSManagedObjectContextObjectsDidChangeNotification payload. Although I was able to get this to work (for some nominal definition of 'work'), I saw big problems looming on the horizon with this approach. Specifically, it's easy to migrate those changes once but what if it changes again before a save operation? Then I'd be stuck maintaining a long lived mapping of OIDs in the source context to OIDs in the destination context(s). This got ugly really fast. If anyone's interested, here's what I came up with:
#interface NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification;
#end
#implementation NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification
{
if (![NSManagedObjectContextObjectsDidChangeNotification isEqual: notification.name])
return;
if (notification.object == self)
return;
NSManagedObjectContext* sourceContext = (NSManagedObjectContext*)notification.object;
NSAssert(self.persistentStoreCoordinator == sourceContext.persistentStoreCoordinator, #"Can't merge changes between MOCs with different persistent store coordinators.");
[sourceContext lock];
// Create object in the local context to correspond to inserted objects...
NSMutableDictionary* foreignOIDsToLocalOIDs = [NSMutableDictionary dictionary];
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSInsertedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [[[NSManagedObject alloc] initWithEntity: foreignMO.entity insertIntoManagedObjectContext: self] autorelease];
[foreignOIDsToLocalOIDs setObject: localMO.objectID forKey: foreignOID];
}
// Bring over all the attributes and relationships...
NSMutableSet* insertedOrUpdated = [NSMutableSet set];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSInsertedObjectsKey]];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSUpdatedObjectsKey]];
for (NSManagedObject* foreignMO in insertedOrUpdated)
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObjectID* localOID = [foreignOIDsToLocalOIDs objectForKey: foreignOID];
localOID = localOID ? localOID : foreignOID;
NSManagedObject* localMO = [self objectWithID: localOID];
// Do the attributes.
[localMO setValuesForKeysWithDictionary: [foreignMO dictionaryWithValuesForKeys: [[foreignMO.entity attributesByName] allKeys]]];
// Do the relationships.
NSDictionary* rByName = foreignMO.entity.relationshipsByName;
for (NSString* key in [rByName allKeys])
{
NSRelationshipDescription* desc = [rByName objectForKey: key];
if (!desc.isToMany)
{
NSManagedObject* relatedForeignMO = [foreignMO valueForKey: key];
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[localMO setValue: localRelatedMO forKey: key];
}
else
{
id collection = [foreignMO valueForKey: key];
id newCollection = [NSMutableSet set];
if ([collection isKindOfClass: [NSOrderedSet class]])
{
newCollection = [NSOrderedSet orderedSet];
}
for (NSManagedObject* relatedForeignMO in collection)
{
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[newCollection addObject: localRelatedMO];
}
[localMO setValue: newCollection forKey: key];
}
}
}
// And delete any objects which pre-existed in my context.
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSDeletedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [self existingObjectWithID: foreignOID error: NULL];
if (localMO)
{
[self deleteObject: localMO];
}
}
[sourceContext unlock];
}
#end
Conclusion
Between the improvements in concurrency management and this parent/child feature, I quickly lost interest pursuing a pre-Lion solution. I'm beginning to gather that the pre-Lion solution would effectively be "Don't use NSPersistentDocument." As best I can tell, all these pain points go away if I drop that requirement. Without that, you can save contexts and migrate stores whenever you want, but naturally you would have to do all that work yourself.
The problem here is that the default NSManagedObjectContext that is created by the NSPersistentDocument is of concurrency type NSConfinementConcurrencyType. CoreData does not allow to create a child context of a context with that type.
As a workaround, this is working for me. The NSManagedObjectContext is created by your NSPersistentDocument, so you can override that method:
- (NSManagedObjectContext *)managedObjectContext {
if (!_context) {
NSManagedObjectContext *_default = [super managedObjectContext];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_context.persistentStoreCoordinator = _default.persistentStoreCoordinator;
}
return _context;
}
I had no idea where to get the persistentStoreCoordinator from, so I called the super implementation and get it there from. With this context you should be able to create child context from that you can use in background operations.
If you don't have a store then you can't save. It seems that you want to save the document in order to merge the changes made on a background thread; well, you can merge those changes manually. When the background thread completes, tell the main thread which objects have been updated / inserted and then make the same changes on the main thread.
If the changes are near arbitrary and thus tedious to duplicate, you can even construct your own NSManagedObjectContextDidSaveNotification on the background thread and then merge it using -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] on the main thread.
I found a very nicely written-up solution along the lines of ipmcc's solution (also running the NSPersistentDocument's MOC in a background thread, and making it the parent context of the main thread's MOC) here.
It includes the full BSD-licensed code for a document app based on that (basically a Mac OS version of iOS's UIManagedDocument).

Core Data NSManagedObject Insert and Save Methods

Is there an easier way to do something like the following in Core Data:
Entry *entry = [[Entry alloc] init];
entry.name = #"An Entry";
[entry save];
I realize you don't have to allocate an NSManagedObject, have to insert directly into the context like the following:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:[self managedObjectContext]];
But this is a lot of code. Also I would like to save by just messaging rather than have to save the entire context:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Could I put these in an NSManagedObject abstract class and have my managed objects extend that abstract class? Basically, I'm just trying to encapsulate more in my models and write less code in my controllers. Any help appreciated.
You can define your own subclass of NSManagedObject, and set all of your entities to use it.
The subclass can have whatever initialiser/save patterns you define, so long as it calls the proper parent class's initialiser.
You could have a +entity method, which might link to a statically defined context (this will restrict you to a single managed object context of course, but that's not always bad as long as you can also call the more primitive initialisers when you need a second context).
You might even have a +entityWithName: method.
As for saving the context, once again you can always define a subclass and add a simple -save method which saves the context and throws an exception if the save fails. You may choose to do this with a category extending the NSManagedObject class, instead of a subclass.
Note it is impossible to save just the change you made to that one entry object. You can only save all changes to an entire managed object context. If you need to save a single record, then you need to create a temporary managed object context, make a single change in it, then save the temporary context, and then sync the temporary context change over to all other managed object context's that currently exist in the app (there is an API to do make this complicated process relatively easy).
I don't like the code you posted to save the context, for several reasons:
Don't define a managedObjectContext variable that just points to self.managedObjectContext. In almost all situations that's an extra line of code for no benefit. At best you're making your code hard to read, at worst you might be introducing bugs.
Why are you checking if it is nil? Usually you should design your code so that it cannot ever be nil. Do the nil check in the constructor of your object, and if it's nil that is a critical failure. Your whole app is completely useless for the user, and you should make it clear to the user that they can't use the app by doing something drastic, such as a crash (an error alert first would be nice, but I wouldn't bother. I'd just throw an exception).
Why are you doing a check for hasChanges? I cannot think of many situations where you would need to do this check. Perhaps your app is allowing a user to make many changes, and then saving them several minutes later? This is bad. The context should be changed milliseconds after a group of changes are made, or else you're risking data loss. Your app could crash, the phone could run out of battery, or the user might receive a phone call and your app is consuming enough RAM that the OS will terminate it instantly in order to present the "incoming call" screen. You shouldn't need to check for hasChanges because you always perform a save operation immediately after making some changes.
As I kind of mentioned before, if the save fails you should present an error to the user then throw an exception. Avoid using NSLog() in deployment code, it's really only useful for development and beta builds.
Check out
NSManagedObject+ActiveRecord.h
Inside Restkit : http://restkit.org/
It is based on :
https://github.com/magicalpanda/MagicalRecord
I am using in RestKit app, but you can adopt it quite easily.
Good luck

Resources