Search code examples
swiftcore-dataswiftuimanagedobjectcontext

Why saving managed object context changes isDeleted value?


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?


Solution

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