Core Data: should I be fetching objects from the parent context or does the child context have the same objects as the parent?
Asked Answered
J

1

9

I am slightly confused about parent/child contexts for ManagedObjectContext.

When I setup a child context and set the parent context, does the child context contain all the objects of the parent context? I am using the stock Core Data methods that get created in the AppDelegate, but I changed the ConcurrencyQueue to main.

In my method that is supposed to update the db:

  • Create child context, set parent context
  • Perform block on child context
  • Fetch from parent context
  • Create or update object in child context
  • Call save on child context
  • Have Notification listener to handle child context saves
  • Save parent context

My issue is that it does not look I am saving anything to the child context. I am not getting my println messages of Update or Create ChatMessage. What am I doing wrong here?

AppDelegate Core Data methods

lazy var managedObjectContext: NSManagedObjectContext? = {
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        if coordinator == nil {
            return nil
        }

        var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = coordinator
        managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)

        return managedObjectContext
    }()

    func contextDidSave(notification: NSNotification) {
        let sender = notification.object as! NSManagedObjectContext
        if sender != managedObjectContext {
            managedObjectContext?.mergeChangesFromContextDidSaveNotification(notification)
            println("Core Data: merging changes from child context")
            saveContext()
        }
    }

Database class that handles the update

lazy var parentContext: NSManagedObjectContext? = {
        if let managedObjectContext = self.appDelegate.managedObjectContext {
            return managedObjectContext
        }
        else {
            return nil
        }
        }()

func updateMessage(chatMessage: ChatMessage) {
        if chatMessage.id.isEmpty { return }

        let childContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
        childContext.parentContext = parentContext
        childContext.performBlock({
            let objectIdDesc = NSExpressionDescription()
            objectIdDesc.name = "objectID"
            objectIdDesc.expression = NSExpression.expressionForEvaluatedObject()
            objectIdDesc.expressionResultType = NSAttributeType.ObjectIDAttributeType

            let fetchRequest = NSFetchRequest(entityName: "ChatMessage")
            fetchRequest.predicate = NSPredicate(format: "id == %@", chatMessage.id)
            fetchRequest.propertiesToFetch = [objectIdDesc]
            fetchRequest.resultType = .DictionaryResultType

            var error: NSError?
            if let results = self.parentContext!.executeFetchRequest(fetchRequest, error: &error) {
                if error == nil {
                    if !results.isEmpty {
                        if let objectId = results[0].valueForKey("objectID") as? NSManagedObjectID {
                            let fetched = childContext.objectWithID(objectId) as! ChatMessage
                            fetched.id = chatMessage.id
                            fetched.senderUserId = chatMessage.senderUserId
                            fetched.senderUsername = chatMessage.senderUsername
                            fetched.receiverUserId = chatMessage.receiverUserId
                            fetched.receiverUsername = chatMessage.receiverUsername
                            fetched.messageType = chatMessage.messageType
                            fetched.message = chatMessage.message
                            fetched.timestamp = chatMessage.timestamp
                            fetched.filepath = chatMessage.filepath
                            println("Updated ChatMessage: \(fetched.id)")
                        }
                        else {
                            var newMessage = NSEntityDescription.insertNewObjectForEntityForName("ChatMessage", inManagedObjectContext: childContext) as! ChatMessage
                            newMessage.id = chatMessage.id
                            newMessage.senderUserId = chatMessage.senderUserId
                            newMessage.senderUsername = chatMessage.senderUsername
                            newMessage.receiverUserId = chatMessage.receiverUserId
                            newMessage.receiverUsername = chatMessage.receiverUsername
                            newMessage.messageType = chatMessage.messageType
                            newMessage.message = chatMessage.message
                            newMessage.timestamp = chatMessage.timestamp
                            newMessage.filepath = chatMessage.filepath
                            println("Create ChatMessage: \(newMessage.id)")
                        }
                    }
                }
                else {
                    println("Fetch Message Object ID Error: \(error?.localizedDescription)")
                }
            }
        })
        childContext.save(nil)
    }
Jejunum answered 6/5, 2015 at 23:29 Comment(0)
R
11

It does not seem to make too much sense to create a child context and then fetch from the parent context. I do not believe that this is the way child contexts' blocks were conceived to be used.

To clear up the confusion: after creating the child context from a parent context, that child context has the same "state" as the parent. Only if the two contexts do different things (create, modify, delete objects) the content of the two contexts will diverge.

So for your setup, proceed as follows:

  • create the child context
  • do the work you want to do (modifying or creating objects from the downloaded data),
  • save the child context

At this stage, nothing is saved to the persistent store yet. With the child save, the changes are just "pushed up" to the parent context. You can now

  • save the parent context

to write the new data to the persistent store. Then

  • update your UI,

best via notifications (e.g. the NSManagedObjectContextDidSaveNotification).

Rascon answered 7/5, 2015 at 6:38 Comment(2)
Thanks for the thorough explanation! So when I do childContext.parentContext = parentContext this will automatically create the same state as the parent? Or will just initializing the child do that?Jejunum
Well, both. You initialize the child context in two steps. First you call alloc-initWithConcurrencyType, then you assign the parent. Before that you cannot really use it because it will not have a model or persistent store.Rascon

© 2022 - 2024 — McMap. All rights reserved.