0

To start with, I don't believe this is a duplicate of: Updating object value in core data is also creating a new object (It's in Obj-C and they were calling insertNewObject every time they segued.)

Background Info: I learned how to use CoreData from the Ray Wenderlich book and referred to it when writing this code. I rolled my own custom stack as outlined in Chapter 3 if you have the book. I can show the code for this if needed.

Queue is the Entity I'm trying to update.

It has 1 property: name - String

And 1 to-many relationship: tasks: Task enter image description here enter image description here enter image description here My CoreData logic is in a Struct which contains the managedContext.

I have a basic find/create function to create a Queue object. This works. It creates 1 and only 1 object.

func findOrCreateMainQueue() -> Queue? {
    let queue = Queue(context: managedContext)
    queue.name = "Queue32"

    let queueFetch: NSFetchRequest<Queue> = Queue.fetchRequest()
    queueFetch.predicate = NSPredicate(format: "%K == %@", #keyPath(Queue.name), "Queue32" as CVarArg)
    do {
        let results = try managedContext.fetch(queueFetch)
        print(results.count)

        if results.count > 0 {
            return results.first!
        } else {
            try managedContext.save()
        }

    } catch let error as NSError {
        print("Fetch error: \(error) description: \(error.userInfo)")
    }

    return nil
}

(As you can see by the queue.name suffix number I have tried a lot of different things.)

I have tried just about everything I can think of:

This code is basically copy/pasted from: How do you update a CoreData entry that has already been saved in Swift?

func addTaskToMainQueue2(task: Task) {
    let queueFetch: NSFetchRequest<Queue> = Queue.fetchRequest()
    queueFetch.predicate = NSPredicate(format: "%K == %@", #keyPath(Queue.name), "Queue32" as CVarArg)
    do {
        let results = try managedContext.fetch(queueFetch)
        print(results.count)

        if results.count > 0 {

            var tasks = results[0].tasks?.mutableCopy() as? NSMutableOrderedSet
            tasks?.add(task)
            results[0].setValue(tasks, forKey: "tasks")
        }

    } catch let error as NSError {
        print("Fetch error: \(error) description: \(error.userInfo)")
    }

        do {
            try managedContext.save()
        } catch let error as NSError {
            print("Save error: \(error),description: \(error.localizedDescription)")
        }


}

Which causes a second Queue object to be created with the "Queue32" name.

Here is another thing I tried:

func addTaskToMainQueue(task: Task) {
    if var queue = findOrCreateMainQueue() {
        var tasks = queue.tasks?.mutableCopy() as? NSMutableOrderedSet
        tasks?.add(task)
        queue.tasks = tasks
        do {
            try managedContext.save()
        } catch let error as NSError {
            print("Save error: \(error),description: \(error.localizedDescription)")
        }

    }
}

For the sake of space I won't add code for other things I've tried.

  • I've tried using the find/create function and updating in that method.
  • I've tried saving the queue as a local object and passing it to the addTask function which causes duplication as well.
  • It also doesn't matter if I pass in the Task or create one in the addTask function.

I am starting to believe my issue is something in my dataModel file causing this as I've tried a number of 'How to update a Core Data object' tutorials online and I get the same result each time.

awakeFromInsert() is called whenever I try to update an object. Not sure if this should be happening.

In other places in my app updating works. For example, if I add a Subtask to a Task. It works fine. However, if I want to change the name of another entity called Project the object duplicates. (Project has an id attribute which is fetched, then the name attribute is changed.)

Thank you in advance. I've been pulling my hair out for hours.

Ian Kohlert
  • 484
  • 1
  • 4
  • 15

1 Answers1

1

I admit not having read all of your code but if you create a new managed object like this

let queue = Queue(context: managedContext)

then it will be added to the managedContext and will be saved to disk at some point. So this code

if results.count > 0 {
    return results.first!
} else {
    try managedContext.save()
}

is irrelevant in regard to the queue object created earlier because it will be saved even if results.count is > 0, although at a later point. So this means you will have to delete queue when the fetch is successful which feels unnecessary, better to wait with creating it

if results.count > 0 {
    return results.first!
} else {
    let queue = Queue(context: managedContext)
    queue.name = "Queue32"
    try managedContext.save()
}

Off topic but I see you return nil if a new object was created rather than fetched, is this intended?

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52