SwiftUI holding reference to deleted core data object causing crash
I basically had the same issue. It seems that SwiftUI loads every view immediately, so the view has been loaded with the Properties of the existing CoreData Object. If you delete it within the View where some data is accessed via @ObservedObject, it will crash.
My Workaround:
- The Delete Action - postponed, but ended via Notification Center
Button(action: {
//Send Message that the Item should be deleted
NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)
//Navigate to a view where the CoreDate Object isn't made available via a property wrapper
self.presentationMode.wrappedValue.dismiss()
})
{Text("Delete Item")}
You need to define a Notification.name, like:
extension Notification.Name {
static var didSelectDeleteItem: Notification.Name {
return Notification.Name("Delete Item")
}
}
- On the appropriate View, lookout for the Delete Message
// Receive Message that the Disease should be deleted
.onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in
//1: Dismiss the View (IF It also contains Data from the Item!!)
self.presentationMode.wrappedValue.dismiss()
//2: Start deleting Disease - AFTER view has been dismissed
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
}
- Be safe on your Views where some CoreData elements are accessed - Check for isFault!
VStack{
//Important: Only display text if the disease item is available!!!!
if !diseaseDetail.isFault {
Text (self.diseaseDetail.text)
} else { EmptyView() }
}
A little bit hacky, but this works for me.
Crash when delete a record from coredata in SwiftUI
I had the same problem as you.
Probably it is a bad idea to use your own id
property to make it Identifiable
because Core Data is setting all the properties to nil
when the object is deleted, even if you declared it differently in your Swift CoreData class.
When deleting your entity, the id
property gets invalidated and the objects .isFault
property is set to true
, but the SwiftUI ForEach
still holds some reference to this ID object (=your UUID) to be able to calculate the "before" and "after" state of the list and somehow tries to access it, leading to the crash.
Therefore the following recommendations:
- Protect the detail view (in the
ForEach
loop by checkingisFault
:
if entity.isFault {
EmptyView()
}
else {
// your regular view body
}
- Expect your
id
property to benil
, either by defining it accordingly in your core data model as optional
@NSManaged public var id: UUID?
or by not relying on the Identifiable
protocol in the SwiftUI ForEach
loop:
ForEach(entities, id: \.self) { entity in ... }
or
ForEach(entities, id: \.objectID) { entity in ... }
Conclusion: you really do not need to make all your CoreData properties Swift Optional
s. It's simply important that your id
property referenced in the ForEach
loop handles the deletion (=setting its value to nil
) gracefully.
Swift Combine: sink() called after core data entity deletion
You can use faultingState
to track this scenario. From documentation:
0
if the object is fully initialized as a managed object and not transitioning to or from another state, otherwise some other value. This property allows you to determine if an object is in a transitional phase when receiving a key-value observing change notification.
So you can ignore this event like this:
.sink { [weak self] sold in
guard let self = self, car.faultingState == 0 else { return }
//
}
If you wanna actually cancel this sink, you can store cancellables inside the object so you can cancel them during prepareForDeletion
.
To do this you need to change object code generation, more info can be found here. Change to Category/Extension - in this case you can create a class and override prepareForDeletion
, and Xcode will still generate all the properties for you.
Now you can move all publisher logic into your class:
@objc(Car)
public class Car: NSManagedObject {
private var subscribers = Set<AnyCancellable>()
func observe<Value: Equatable>(keyPath: KeyPath<Item, Value>, receiveValue: @escaping ((Value) -> Void)) {
publisher(for: keyPath, options: [.new])
.removeDuplicates()
.receive(on: RunLoop.main)
.sink(receiveValue: receiveValue)
.store(in: &subscribers)
}
public override func prepareForDeletion() {
super.prepareForDeletion()
subscribers.removeAll()
}
}
Or just use it to store cancellables.
Why saving managed object context changes isDeleted value?
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. Declarationvar 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.
Related Topics
Check If User Is Logged into Icloud? Swift/Ios
Does App Store Reject Submission If Nsallowsarbitraryloads Set to Yes
Using Core Data, Icloud and Cloudkit for Syncing and Backup and How It Works Together
What #Defines Are Set Up by Xcode When Compiling for Iphone
Cropping Image with Swift and Put It on Center Position
How to Add Iphonex Launch Image
Performseguewithidentifier Very Slow When Segue Is Modal
Fade Out Scrolling Uitextview Over Image
Https iOS with Self Signed Certificate
Navigation Bar Under Status Bar After Video Playback in Landscape Mode
Nsdateformatter: Date According to Currentlocale, Without Year
Uiview Vertical Flip Animation
Silent Push Notifications Only Delivered If Device Is Charging And/Or App Is Foreground
Checking a Null Value in Objective-C That Has Been Returned from a JSON String
How to Load Local PDF in Uiwebview in Swift
Uikeyboardframebeginuserinfokey & Uikeyboardframeenduserinfokey