【发布时间】:2017-03-23 13:40:34
【问题描述】:
我有两个实体(类别、事件),它们具有双向多对多关系。一个类别可以有多个事件,一个事件可以有多个类别。从某种意义上说,类别与事件的关系是可选的,类别可以在没有事件的情况下存在,但事件与类别的关系是强制性的(没有类别,事件就不能存在)。我正在尝试插入事件并向它们添加类别,但出现 NSValidationErrorValue=Relationship 错误。这是我的代码:
private func storeEventsXMLStream(_ xml: XMLIndexer) {
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator
// Remove all data before inserting
// This line of code is necessary because data needs to be downloaded on daily basis.
// Otherwise, I will get redundant data.
removeAllExistingData("Event_Ottawa", managedObjectContext: managedObjectContext)
autoreleasepool { // Scoping is necessary to fix memory leak
for xmlcat in xml["events"]["event"]{
let event = NSEntityDescription.insertNewObject(forEntityName: "Event_Ottawa", into: managedObjectContext) as! Event_Ottawa
event.id = Int32((xmlcat.element?.attribute(by: "id")?.text)!)!
event.website_url_english = xmlcat["website_url_english"].element?.text
event.website_url_french = xmlcat["website_url_french"].element?.text
// setting other attributes of events here. Exactly like I did in above 3 line
// Just another attribute. Storing it a String in Coredata
var recur_rules = ""
for rule in xmlcat["recur_rules"]["recur_rule"] {
recur_rules += (rule.element?.attribute(by: "weekday")?.text)!
}
if !recur_rules.isEmpty {
event.recur_rules = recur_rules
}
do {
var predicateArray:[NSPredicate] = []
// Categories are inserted to the Coredata before this method call. So I'm fetching the applicable one here.
for category in xmlcat["categories"]["category"] {
let predicate = NSPredicate(format: "id = %@", (category.element?.attribute(by: "id")?.text)!)
predicateArray.append(predicate)
}
let requestCategory:NSFetchRequest<Category_Event_Ottawa> = Category_Event_Ottawa.fetchRequest()
requestCategory.predicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicateArray)
let managedContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
managedContext.persistentStoreCoordinator = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator
let applicableCategories = try managedContext.fetch(requestCategory)
for category in applicableCategories {
event.addToCategory(category)
}
}
} catch {
print(error)
}
}
}
// only save once per batch insert
do {
try managedObjectContext.save()
} catch {
print(error)
}
managedObjectContext.reset() <-- I get EXC_BAD_ACCESS, when I use the same object context for fetching as insertion
}
我收到的错误:
Error Domain=NSCocoaErrorDomain Code=1560 "(null)" UserInfo={NSDetailedErrors=(
"Error Domain=NSCocoaErrorDomain Code=1550 \"The operation couldn\U2019t be completed. (Cocoa error 1550.)\" UserInfo={Dangling reference to an invalid object.=null, NSValidationErrorValue=Relationship 'category' on managed object (0x6100050963f0)
如果对插入和提取使用相同的托管对象上下文,我不会再收到此错误。但是我从重置上下文对象的行中得到 EXC_BAD_ACCESS。我从另一篇文章中了解到 CoreData 不是线程安全的。所以也许这是一个问题。但是我该如何解决这个问题?如果相关,Event-Category 关系的删除规则为 Nullify,Category-Event 为 Cascade。
【问题讨论】:
-
一些问题/cmets: 1) 当您向数据模型文件添加新关系时,
Optional属性默认处于选中状态 - 您在某个时候没有取消选中它吗?这将是您收到关系错误的最简单原因。 2) 无论错误如何,当您在上下文中调用reset()时,您尝试做什么/期望发生什么? -
3) 我仍然熟悉使用
NSPersistentContainer类,但是有什么理由你以这种方式创建上下文而不是使用PersistentContainer.viewContext作为主要上下文并使用newBackgroundContext()创建示例代码显示的背景上下文? -
4) 重新线程安全,对于在后台/私有线程上工作的任何上下文,您应该在上下文的
perform(异步)或performAndWait(同步)闭包内执行操作,以防止线程-相关错误developer.apple.com/reference/coredata/nsmanagedobjectcontext/… -
@MatthewS : 1 ) 我确保已设置关系选项以反映我的需求。所以是的,Category->Event 是可选的,Event->Category 不是可选的。 2)我想确保当我再次执行coredata相关操作时没有旧数据的痕迹留在内存中。 3) 我也是 NSPersistentContainer 的新手。我的代码基于我从 SO 中学到的帖子。我读到这种方法可以让我在后台而不是 UI 线程中执行,并且我有 200k 行要插入。我不知道这种方法和你的方法有什么区别。我必须调查一下。并感谢您的链接!
-
创建上下文的方式可能并没有什么不同——我只是没这么看......所以可能是一个红鲱鱼。但我认为,如果您在
managedObjectContext.perform闭包范围内调用reset(),那么我认为这将防止 BAD_ACCESS 错误。此外,当您一次导入 100k+ 条这样的记录时,还要确保您尝试批量大小(例如,每 1000 个对象调用保存/重置以保持低内存使用率)