3lvis / DATAStack

100% Swift Simple Boilerplate Free Core Data Stack. NSPersistentContainer
Other
214 stars 44 forks source link

Create new main context #74

Closed Sorix closed 8 years ago

Sorix commented 8 years ago

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 create newDisposableMainContext, but it is detached from saving to disk, so I can’t merge changes in it to my default mainContext.

3lvis commented 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()
}
3lvis commented 8 years ago

I hope this was useful!

Sorix commented 8 years ago

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
    }
Sorix commented 8 years ago

@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.

Example

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.

3lvis commented 8 years ago

@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.

batjo commented 6 years ago

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!

3lvis commented 6 years ago

@batjo Hi Bart! What about setting the relationship after the transaction is confirmed?

3lvis commented 6 years ago

Otherwise you might have to make a copy of all the relationships as well.

batjo commented 6 years ago

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.

3lvis commented 6 years ago

@batjo Sounds like something that we are already doing. Might be worth to take a look if this is possible with the current setup.

3lvis commented 6 years ago

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.