Closed Sorix closed 8 years ago
@Sorix Hi! I use newDisposableMainContext
for this exact purpose. It's safer than having a different mainContext
since it doesn't trigger any KVO calls that could affect your UI bound to mainContext
. Then you could pass data to the main context like this.
let temporaryEntity = NSEntityDescription.entityForName("User", inManagedObjectContext: dataStack.newDisposableMainContext)!
let temporaryObject = NSManagedObject(entity: entity, insertIntoManagedObjectContext: dataStack.newDisposableMainContext)
//.. do discardable changes
//.. when user presses "Save" and the server accepts the changes then
self.dataStack.performInNewBackgroundContext { backgroundContext in
let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)!
let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext)
// Add pod: https://github.com/hyperoslo/NSManagedObject-HYPPropertyMapper
let objectJSON = temporaryObject.hyp_dictionary()
object.hyp_fillWithDictionary(objectJSON)
try! backgroundContext.save()
}
I hope this was useful!
It’s up to you to decide, but I think it will be better to give an option to use create new main or disposable context, because not everybody want to use Core Data → JSON → Core Data transformation and adding new module (and hyp has some issues too, it’s crashed on my application on Many-To-Many relations often).
You can add something like this (haven’t tested it, just for example) and that will be the option :)
/**
Return new context for the main queue. Please do not use this to mutate data, use `performInNewBackgroundContext`
instead.
*/
public func newMainContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.persistentStoreCoordinator = self.persistentStoreCoordinator
context.undoManager = nil
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DATAStack.mainContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: context)
return context
}
@3lvis I finally agree with you, there is no way no make new mainContext and write data to it from UI, I have struggled with it for 2 days. We can write data to that main context safely, but when we will trigger save()
UI may freeze while Core Data making HDD/SSD operations.
Only way is to «save» context from background thread, but we can’t do so because we’ve create context in main thread.
It will be ideal if we can write to main context in main thread (if we’re using CoreData to store UI changes like text editing) and save that data in background thread, but as far as I know there is no possiblity for that.
Why we need to mutate data in main thread? For example we have table with 100 cells and we can edit any data in it «on-the-fly». We edited CELL1, than scrolled to bottom and edited CELL100. When we will scroll again to top, our CELL1 will be regenerated (it has been released from memory in most cases) and we need to get text data from main context.
Solution: we can read data in new main context, save data in background context and trigger .save()
after each edit to merge changes, but we will loose .reset()
option in new main context, that is very important to my goals.
@Sorix I'm happy that there are developers like you, that take some time to understand the pros and cons of adding things to an open source library. Many would have expected the change to get added, then realize that this doesn't help them and never write back.
I remember hitting a similar spot as the one you mentioned. At the time my best solution was the one that I mentioned, Core Data → JSON → Core Data, other solution would be creating ViewModels for this specific changes but that adds a lot of boilerplate.
Hi @Sorix @3lvis, thanks for the explanation, makes it all clear. The only thing I'm struggling with is how to make a relations between objects in the mainContext
and in the disposableMainContext
.
For example:
let temporaryEntity = NSEntityDescription.entityForName("User", inManagedObjectContext: disposableContext)!
let temporaryObject = NSManagedObject(entity: entity, insertIntoManagedObjectContext: disposableContext)
temporaryObject.task = task
// this crashes because of illegal relationship between objects in different contexts
temporaryObject.task = disposableContext.object(with: task.objectID) as! Task
// this crashes because of Object's persistent store is not reachable from this NSManagedObjectContext's coordinator
I have the feeling I'm missing something but not sure what so any help would be welcome!
@batjo Hi Bart! What about setting the relationship after the transaction is confirmed?
Otherwise you might have to make a copy of all the relationships as well.
Thanks! It sounds a bit complicated, since I want the UI to reflect the changes of the user so setting if after the transaction would require to store all the temporary changes. I was reading a bit more about it and found this article: https://medium.com/companydev/getting-sexy-with-core-data-b009fab6e9b1
From the post:
We have a single parent context of type NSPrivateQueueConcurrencyType and this context is associated with the persistent store coordinator, we call this property the privateQueueManagedObjectContext. We keep this context private to the CoreDataController class since it is more of an implementation detail. We then have one public NSMainQueueManagedObjectContext property named mainQueueManagedObjectContext that is a child of our privateQueueManagedObjectContext, all changes from the mainQueueManagedObjectContext are pushed up to the privateQueueManagedObjectContext and written to disk on the private queue using a background thread.
This sounds as a solution to the problem don't you think? If you agree I should be able to help with a pull request.
@batjo Sounds like something that we are already doing. Might be worth to take a look if this is possible with the current setup.
Clarifying: We already do a similar set up than what the post says, what I meant is that might be interesting to see if making a child context could work better than having the disposable context.
Hello! Is it the way to create new context, that can be used in main thread in DATAStack? For example I have «Edit item» view, I load all data there in separate context and if user doesn’t click «Save» all changes in that separate context are reset.
I’ve found only way to create separate background context (
newBackgroundContext
), or createnewDisposableMainContext
, but it is detached from saving to disk, so I can’t merge changes in it to my default mainContext.