As I struggled with developing my application I decided to learn some basics and created playground project using Core Data in SwiftUI, but I encountered problem I can’t resolve and even root cause. Let me begin with describing what I tried to achieve and what the problem is.
I created 3 models:
ListEntity– have name and to-many relationship toListItemEntityListItemEntity– has just flag and 2 to-one relationship toListEntityandNameEntityNameEntity– has just name and to-many relationship toListItemEntity
For those models I created 3 basic views to show and add data. Views for showing and adding ListEntity (first for list of them and second with their details) works fine without any problem. The problem I have is with 3rd view which consists of searchable list of NameEntity (with add button, which just add new NameEntity of name Name <counts of names>). Each NameEntity row in list is clickable and by clicking it you should add (or delete) ListItemEntity to currently edited ListEntity with selected NameEntity. That works fine as long as you add only 1 item. When I try to add another I get error somewhere in context save method. The problem is, my save method is inside do { } catch { } block and it should print error to console, but instead it crashes whole application.
I created data in my persistent controller with more than just one ListItemEntity and it worked without problem:
let list = ListEntity(context: viewContext)
list.name = "List 1"
viewContext.forceSave()
var names = [NameEntity(context: viewContext), NameEntity(context: viewContext)]
for name in names {
name.name = "Name \(names.firstIndex(of: name) ?? 0)"
}
viewContext.forceSave()
let items = [ListItemEntity(context: viewContext), ListItemEntity(context: viewContext)]
for item in items {
item.flag = false
item.list = list
item.name = names.popLast()
}
viewContext.forceSave()
Here is error I’m getting:
error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[NameEntity compare:]: unrecognized selector sent to instance 0x600002118ff0 with userInfo (null)
With call stack:
*** First throw call stack:
(
0 CoreFoundation 0x0000000180491128 __exceptionPreprocess + 172
1 libobjc.A.dylib 0x000000018008412c objc_exception_throw + 56
2 CoreFoundation 0x00000001804a5f78 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
3 CoreFoundation 0x0000000180495278 ___forwarding___ + 1280
4 CoreFoundation 0x000000018049759c _CF_forwarding_prep_0 + 92
5 Foundation 0x0000000180dde258 _NSCompareObject + 60
6 CoreData 0x00000001863c8564 +[NSFetchedResultsController _insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 220
7 CoreData 0x00000001863c82b4 -[NSFetchedResultsController _updateFetchedObjectsWithInsertChange:] + 832
8 CoreData 0x00000001863c9e00 __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 2364
9 CoreData 0x0000000186369e80 developerSubmittedBlockToNSManagedObjectContextPerform + 156
10 CoreData 0x0000000186369d5c -[NSManagedObjectContext performBlockAndWait:] + 212
11 CoreData 0x00000001863c94a8 -[NSFetchedResultsController _core_managedObjectContextDidChange:] + 96
12 CoreFoundation 0x00000001803c1878 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 140
13 CoreFoundation 0x00000001803c179c ___CFXRegistrationPost_block_invoke + 84
14 CoreFoundation 0x00000001803c0c8c _CFXRegistrationPost + 404
15 CoreFoundation 0x00000001803c0668 _CFXNotificationPost + 688
16 Foundation 0x0000000180d84cb4 -[NSNotificationCenter postNotificationName:object:userInfo:] + 88
17 CoreData 0x000000018635ce54 -[NSManagedObjectContext _postObjectsDidChangeNotificationWithUserInfo:] + 320
18 CoreData 0x000000018636f640 -[NSManagedObjectContext _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1244
19 CoreData 0x000000018635eb9c -[NSManagedObjectContext _processRecentChanges:] + 2884
20 CoreData 0x0000000186360cc0 -[NSManagedObjectContext save:] + 340
21 Playground 0x00000001024b8da8 $sSo22NSManagedObjectContextC10PlaygroundE12saveIfNeededyyKF + 108
22 Playground 0x00000001024b8eac $sSo22NSManagedObjectContextC10PlaygroundE9forceSaveyyF + 60
23 Playground 0x00000001024c3d30 $s10Playground8NameTileV4bodyQrvgyycfU_ + 1220
24 SwiftUI 0x00000001c5908800 OUTLINED_FUNCTION_11 + 620
25 SwiftUI 0x00000001c58365c0 OUTLINED_FUNCTION_31 + 1824
26 SwiftUI 0x00000001c51f742c OUTLINED_FUNCTION_21 + 32
27 SwiftUI 0x00000001c4f04840 OUTLINED_FUNCTION_2 + 6392
28 SwiftUI 0x00000001c4f0c7c4 OUTLINED_FUNCTION_2 + 39036
29 SwiftUI 0x00000001c51f742c OUTLINED_FUNCTION_21 + 32
30 SwiftUI 0x00000001c51f7448 OUTLINED_FUNCTION_21 + 60
31 SwiftUI 0x00000001c51f742c OUTLINED_FUNCTION_21 + 32
32 SwiftUI 0x00000001c58f6554 OUTLINED_FUNCTION_17 + 2340
33 SwiftUI 0x00000001c58f6b18 OUTLINED_FUNCTION_17 + 3816
34 SwiftUI 0x00000001c51e3f04 OUTLINED_FUNCTION_7 + 9760
35 SwiftUI 0x00000001c51e9298 OUTLINED_FUNCTION_7 + 31156
36 UIKitCore 0x0000000184a3583c -[UICollectionView _selectItemAtIndexPath:animated:scrollPosition:notifyDelegate:deselectPrevious:performCustomSelectionAction:] + 1176
37 UIKitCore 0x0000000184a650b4 -[UICollectionView touchesEnded:withEvent:] + 452
38 UIKitCore 0x000000018531a718 forwardTouchMethod + 264
39 UIKitCore 0x000000018531a718 forwardTouchMethod + 264
40 UIKitCore 0x000000018531a718 forwardTouchMethod + 264
41 UIKitCore 0x0000000184e30458 _UIGestureEnvironmentUpdate + 5912
42 UIKitCore 0x0000000184e2ea60 -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 288
43 UIKitCore 0x0000000184e2e7d0 -[UIGestureEnvironment _updateForEvent:window:] + 156
44 UIKitCore 0x0000000185329f00 -[UIWindow sendEvent:] + 3088
45 UIKitCore 0x000000018530998c -[UIApplication sendEvent:] + 576
46 UIKitCore 0x000000018538a5c0 __dispatchPreprocessedEventFromEventQueue + 1708
47 UIKitCore 0x000000018538d474 __processEventQueue + 5524
48 UIKitCore 0x0000000185385e38 __eventFetcherSourceCallback + 156
49 CoreFoundation 0x00000001803f1f18 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
50 CoreFoundation 0x00000001803f1e60 __CFRunLoopDoSource0 + 172
51 CoreFoundation 0x00000001803f15d0 __CFRunLoopDoSources0 + 232
52 CoreFoundation 0x00000001803ebcb8 __CFRunLoopRun + 768
53 CoreFoundation 0x00000001803eb5a4 CFRunLoopRunSpecific + 572
54 GraphicsServices 0x000000018e9fbae4 GSEventRunModal + 160
55 UIKitCore 0x00000001852f02e4 -[UIApplication _run] + 868
56 UIKitCore 0x00000001852f3f5c UIApplicationMain + 124
57 SwiftUI 0x00000001c51fc1b0 OUTLINED_FUNCTION_70 + 500
58 SwiftUI 0x00000001c51fc050 OUTLINED_FUNCTION_70 + 148
59 SwiftUI 0x00000001c4f02fa4 OUTLINED_FUNCTION_2 + 92
60 Playground 0x00000001024c9598 $s10Playground0A3AppV5$mainyyFZ + 40
61 Playground 0x00000001024c9648 main + 12
62 dyld 0x0000000102655544 start_sim + 20
63 ??? 0x00000001028660e0 0x0 + 4337328352
64 ??? 0xc719000000000000 0x0 + 14346498087965425664
)
Now here is my code, starting with data definition first:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23C71" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<entity name="ListEntity" representedClassName="ListEntity" syncable="YES">
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="items" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="ListItemEntity" inverseName="list" inverseEntity="ListItemEntity"/>
</entity>
<entity name="ListItemEntity" representedClassName="ListItemEntity" syncable="YES">
<attribute name="flag" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="list" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ListEntity" inverseName="items" inverseEntity="ListEntity"/>
<relationship name="name" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NameEntity" inverseName="items" inverseEntity="NameEntity"/>
</entity>
<entity name="NameEntity" representedClassName="NameEntity" syncable="YES">
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="items" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ListItemEntity" inverseName="name" inverseEntity="ListItemEntity"/>
</entity>
</model>
class DataProvider {
static let shared = DataProvider()
private let container: NSPersistentContainer
var viewContext: NSManagedObjectContext {
container.viewContext
}
private init() {
container = NSPersistentContainer(name: "Data")
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores { description, error in
print(description.url?.path(percentEncoded: false) ?? "No URL")
if let error {
fatalError("Could not load persistent stores: \(error.localizedDescription)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
}
}
extension NSManagedObjectContext {
func saveIfNeeded() throws {
guard hasChanges else { return }
try save()
}
func forceSave() {
do {
try saveIfNeeded()
} catch {
print(error.localizedDescription)
}
}
}
@objc(ListEntity)
public class ListEntity: NSManagedObject {}
public extension ListEntity {
@nonobjc class func fetchRequest() -> NSFetchRequest<ListEntity> {
NSFetchRequest<ListEntity>(entityName: "ListEntity")
}
@NSManaged var name: String?
@NSManaged var items: NSSet?
var wrappedName: String {
name ?? "No name"
}
var wrappedItems: [ListItemEntity] {
if let items = items as? Set<ListItemEntity> {
return items.sorted(using: KeyPathComparator(\.wrappedName))
}
return []
}
}
// MARK: Generated accessors for items
public extension ListEntity {
@objc(addItemsObject:)
@NSManaged func addToItems(_ value: ListItemEntity)
@objc(removeItemsObject:)
@NSManaged func removeFromItems(_ value: ListItemEntity)
@objc(addItems:)
@NSManaged func addToItems(_ values: NSSet)
@objc(removeItems:)
@NSManaged func removeFromItems(_ values: NSSet)
}
extension ListEntity: Identifiable {}
@objc(ListItemEntity)
public class ListItemEntity: NSManagedObject {}
public extension ListItemEntity {
@nonobjc class func fetchRequest() -> NSFetchRequest<ListItemEntity> {
NSFetchRequest<ListItemEntity>(entityName: "ListItemEntity")
}
@nonobjc class func fetchRequestFor(_ list: ListEntity, sortDescriptors: [NSSortDescriptor] = []) -> NSFetchRequest<ListItemEntity> {
let fetchRequest = Self.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "list == %@", list)
fetchRequest.sortDescriptors = sortDescriptors
return fetchRequest
}
@nonobjc class func fetchRequest(with name: NameEntity, on list: ListEntity, sortDescriptors: [NSSortDescriptor] = []) -> NSFetchRequest<ListItemEntity> {
let fetchRequest = Self.fetchRequest()
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "list == %@", list),
NSPredicate(format: "name == %@", name),
])
fetchRequest.sortDescriptors = sortDescriptors
return fetchRequest
}
@NSManaged var flag: Bool
@NSManaged var list: ListEntity?
@NSManaged var name: NameEntity?
var wrappedName: String {
name?.wrappedName ?? "No name"
}
}
extension ListItemEntity: Identifiable {}
@objc(NameEntity)
public class NameEntity: NSManagedObject {}
public extension NameEntity {
@nonobjc class func fetchRequest() -> NSFetchRequest<NameEntity> {
NSFetchRequest<NameEntity>(entityName: "NameEntity")
}
@nonobjc class func fetchRequest(containing name: String, fetchLimit: Int? = nil, sortDescriptors: [NSSortDescriptor] = []) -> NSFetchRequest<NameEntity> {
let fetchRequest = Self.fetchRequest()
if let fetchLimit {
fetchRequest.fetchLimit = fetchLimit
}
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedName.isEmpty {
fetchRequest.predicate = NSPredicate(format: "name CONTAINS[cd] %@", trimmedName)
}
fetchRequest.sortDescriptors = sortDescriptors
return fetchRequest
}
@NSManaged var name: String?
@NSManaged var items: NSSet?
var wrappedName: String {
name ?? "No name"
}
var wrappedItems: [ListItemEntity] {
if let items = items as? Set<ListItemEntity> {
return items.sorted(using: KeyPathComparator(\.wrappedName))
}
return []
}
}
// MARK: Generated accessors for items
public extension NameEntity {
@objc(addItemsObject:)
@NSManaged func addToItems(_ value: ListItemEntity)
@objc(removeItemsObject:)
@NSManaged func removeFromItems(_ value: ListItemEntity)
@objc(addItems:)
@NSManaged func addToItems(_ values: NSSet)
@objc(removeItems:)
@NSManaged func removeFromItems(_ values: NSSet)
}
extension NameEntity: Identifiable {}
Lastly my Views:
struct ListEntitiesView: View {
@Environment(\.managedObjectContext)
private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \ListEntity.name, ascending: true)],
animation: .default
)
private var lists: FetchedResults<ListEntity>
var body: some View {
NavigationStack {
List {
ForEach(lists) { list in
NavigationLink(value: list) {
ListEntityTile(list)
}
}
}
.navigationTitle("ListEntities")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button {
let list = ListEntity(context: viewContext)
list.name = "List \(lists.count + 1)"
viewContext.forceSave()
} label: {
Label("Add List", systemImage: "plus")
}
}
.navigationDestination(for: ListEntity.self) { list in
ListEntityView(list)
}
}
}
}
struct ListEntityTile: View {
@ObservedObject
private var list: ListEntity
@FetchRequest
private var items: FetchedResults<ListItemEntity>
private var totalItems: Int {
items.count
}
private var flaggedItems: Int {
items.filter(\.flag).count
}
init(_ list: ListEntity) {
_list = ObservedObject(wrappedValue: list)
_items = FetchRequest(fetchRequest: ListItemEntity.fetchRequestFor(list))
}
var body: some View {
HStack {
Text(list.wrappedName)
Spacer()
Text("\(flaggedItems) / \(totalItems)")
}
}
}
struct ListEntityView: View {
@Environment(\.managedObjectContext)
private var viewContext
@ObservedObject
private var list: ListEntity
@FetchRequest
private var items: FetchedResults<ListItemEntity>
init(_ list: ListEntity) {
_list = ObservedObject(wrappedValue: list)
_items = FetchRequest(fetchRequest: ListItemEntity.fetchRequestFor(list, sortDescriptors: [NSSortDescriptor(keyPath: \ListItemEntity.name, ascending: true)]))
}
var body: some View {
List {
ForEach(items) { item in
ListItemTile(item)
}
}
.navigationTitle(list.wrappedName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
NavigationLink {
AddListItemEntity(list)
} label: {
Label("Add Item", systemImage: "plus")
}
}
}
}
struct ListItemTile: View {
@Environment(\.managedObjectContext)
private var viewContext
@ObservedObject
private var item: ListItemEntity
init(_ item: ListItemEntity) {
_item = ObservedObject(wrappedValue: item)
}
var body: some View {
Button {
item.flag.toggle()
viewContext.forceSave()
} label: {
Label(item.wrappedName, systemImage: item.flag ? "checkmark.circle" : "circle")
}
}
}
struct AddListItemEntity: View {
@Environment(\.managedObjectContext)
private var viewContext
@ObservedObject
private var list: ListEntity
@State
private var searchName = ""
@FetchRequest(sortDescriptors: [])
private var names: FetchedResults<NameEntity>
init(_ list: ListEntity) {
_list = ObservedObject(wrappedValue: list)
}
var body: some View {
List {
TextField("Search name", text: $searchName.animation())
NamesList(for: list, containing: searchName)
}
.toolbar {
Button {
let name = NameEntity(context: viewContext)
name.name = "Name \(names.count + 1)"
viewContext.forceSave()
} label: {
Label("Add Name", systemImage: "plus")
}
}
}
}
struct NamesList: View {
@ObservedObject
private var list: ListEntity
@FetchRequest
private var names: FetchedResults<NameEntity>
init(for list: ListEntity, containing name: String) {
_list = ObservedObject(wrappedValue: list)
_names = FetchRequest(
fetchRequest: NameEntity.fetchRequest(containing: name, sortDescriptors: [NSSortDescriptor(keyPath: \NameEntity.name, ascending: true)]),
animation: .default
)
}
var body: some View {
ForEach(names) { name in
NameTile(of: name, for: list)
}
}
}
struct NameTile: View {
@Environment(\.managedObjectContext)
private var viewContext
@FetchRequest
private var items: FetchedResults<ListItemEntity>
@ObservedObject
private var list: ListEntity
@ObservedObject
private var name: NameEntity
private var item: ListItemEntity? {
items.first { $0.name == name }
}
init(of name: NameEntity, for list: ListEntity) {
_list = ObservedObject(wrappedValue: list)
_name = ObservedObject(wrappedValue: name)
_items = FetchRequest(
fetchRequest: ListItemEntity.fetchRequestFor(list),
animation: .default
)
}
var body: some View {
Button {
if let item {
viewContext.delete(item)
} else {
let newItem = ListItemEntity(context: viewContext)
newItem.flag = false
newItem.name = name
newItem.list = list
}
viewContext.forceSave()
} label: {
Label(name.wrappedName, systemImage: item != nil ? "checkmark.circle" : "circle")
}
}
}
I believe it must be something simple and obvious as this is very simple use case I think, but my lack of experience get my to point I can’t do anything more than ask for your help.




