无法在 CoreData 中向父级添加第二个子级

问题描述 投票:0回答:1

当我努力开发我的应用程序时,我决定学习一些基础知识并使用 SwiftUI 中的 Core Data 创建游乐场项目,但我遇到了无法解决的问题,甚至是根本原因。让我首先描述我试图实现的目标以及问题是什么。

我创建了 3 个模型:

  • ListEntity
    - 具有名称和与
    ListItemEntity
  • 的多对多关系
  • ListItemEntity
    - 仅具有标志以及与
    ListEntity
    NameEntity
  • 的 2 对一关系
  • NameEntity
    - 只有名称和与
    ListItemEntity
  • 的一对多关系

对于这些模型,我创建了 3 个基本视图来显示和添加数据。用于显示和添加

ListEntity
的视图(首先是它们的列表,第二个是它们的详细信息)工作正常,没有任何问题。我遇到的问题是第三个视图,其中包含可搜索的
NameEntity
列表(带有添加按钮,只需添加名称为
NameEntity
的新
Name <counts of names>
)。列表中的每个
NameEntity
行都是可单击的,通过单击它,您应该将
ListItemEntity
添加(或删除)到当前编辑的
ListEntity
并选择
NameEntity
。只要您仅添加 1 项,就可以正常工作。当我尝试添加另一个时,我在上下文保存方法中的某处出现错误。问题是,我的保存方法位于
do { } catch { }
块内,它应该将错误打印到控制台,但它却使整个应用程序崩溃。

这是我收到的错误:

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)

带有调用堆栈:

*** 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

)

现在这是我的代码,首先从数据定义开始:

<?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 {}

最后我的看法:

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")
        }
    }
}

我认为我一定以某种方式错误配置了

ListItemEntity
ListEntity
的关系,但我设法在我的
DataProvider
中运行此代码并且它工作没有任何问题:

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()

我相信这一定是简单明了的事情,因为我认为这是非常简单的用例,但我缺乏经验,所以我只能寻求你的帮助。

ios swift swiftui core-data
1个回答
0
投票

我发现我的代码有问题。有问题的部分是

ListEntityView
内的排序描述符。有:

NSSortDescriptor(keyPath: \ListItemEntity.name, ascending: true)

其实应该是:

NSSortDescriptor(keyPath: \ListItemEntity.name?.name, ascending: true)

由于

ListItemEntity
中的名称字段是一种指向关系,因此需要从该实体中获取字段才能进行排序。

© www.soinside.com 2019 - 2024. All rights reserved.