3

I am writing an iOS app using SwiftUI and Core Data. I am very new to Core Data and try to understand something:

Why try self.moc.save() changes self.item.isDeleted from true to false? It happens after I delete a Core Data object (isDeleted changes to true), but later saving managed object context changes it to false. Why is that?

Here is an example:

ContentView.swift

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc
    var fetchRequest: FetchRequest<Item>
    var items: FetchedResults<Item> { fetchRequest.wrappedValue }
    
    var body: some View {
        
        NavigationView {
            List {
                ForEach(items, id: \.self) {item in
                    
                    NavigationLink(destination: DetailsView(item: item)) {
                        Text("\(item.name ?? "default item name")")
                    }
                    
                }
            }
            .navigationBarTitle("Items")
            .navigationBarItems(
                leading:
                Button(action: {
                    for number in 1...3 {
                        let item = Item(context: self.moc)
                        item.date = Date()
                        item.name = "Item \(number)"
                        
                        do {
                            try self.moc.save()
                        }catch{
                            print(error)
                        }
                    }
                }) {
                    Text("Add 3 items")
                }
            )
        }
    }
    
    init() {
        fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \Item.name, ascending: true)
        ])
    }
}

DetailsView.swift

import SwiftUI

struct DetailsView: View {
    
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var item: Item
    
    var body: some View {
        
        VStack {
            Text("\(item.name ?? "default item name")")
        }
        .navigationBarItems(
            trailing:
            Button(action: {
                self.moc.delete(self.item)
                print(self.item.isDeleted)

                self.presentationMode.wrappedValue.dismiss()
                print(self.item.isDeleted)
                
                do {
                    try self.moc.save()
                    print(self.item.isDeleted)
                }catch{
                    print(error)
                }
                
            }) {
                Text("Delete")
                    .foregroundColor(.red)
            }
        )
        .onDisappear {
            print(self.item.isDeleted)
            if !self.item.isDeleted {
                print(self.item.isDeleted)
                self.item.name = "new name"
                print(self.item.isDeleted)
                do {
                    try self.moc.save()
                }catch{
                    print(error)
                }
            }
            
        }
        
    }
}

What I expected will happen:

  1. self.moc.delete(self.item) will delete an object and mark self.item.isDeleted as true.
  2. try self.moc.save will save moc
  3. if !self.item.isDeleted will prevent code execution if item is deleted (without this, I was getting an error: Mutating a managed object (...) after it has been removed)

It didn't work. I have added print(self.item.isDeleted) on few lines and breakpoints on those lines to check what exactly happens.

What happened is this:

  1. self.moc.delete(self.item) deleted an object and marked self.item.isDeleted as true.
  2. try self.moc.save saved moc and...
  3. self.item.isDeleted changed to be false
  4. if !self.item.isDeleted didn't prevent code execution, because isDeleted was false at this point.

Is it a bug? Or I don't understand the life cycle of Core Data objects and isDeleted changes as it should?

mallow
  • 2,368
  • 2
  • 22
  • 63
  • 1
    After deleting a CoreData entity, you should not change it anyway. What you can do, is that you store a own value for every entity isDeleted. When you want to remove a item, you just change that bool to true. Then you can use that everywhere in your View/ FetchRequest. You have to be careful as the entity is not getting deleted than. You could remove objects with isDeleted after a while. – davidev Jun 27 '20 at 09:13
  • Thank you, I have solved it in a similar way. I added `@State private var itemExists = true` to the DetailsView and I am changing its value after deletion. – mallow Jun 27 '20 at 09:24

1 Answers1

3

Why try self.moc.save() changes self.item.isDeleted from true to false? It happens after I delete a Core Data object (isDeleted changes to true), but later saving managed object context changes it to false. Why is that?

It behaves as documented - returns true before save, and not in other cases

Here is snapshot of Apple documentation for NSManagedObject:

Summary

A Boolean value that indicates whether the managed object will be deleted during the next save. Declaration

var isDeleted: Bool { get } Discussion

true if Core Data will ask the persistent store to delete the object during the next save operation, otherwise false. It may return false at other times, particularly after the object has been deleted. The immediacy with which it will stop returning true depends on where the object is in the process of being deleted. If the receiver is a fault, accessing this property does not cause it to fire.

Asperi
  • 228,894
  • 20
  • 464
  • 690