ios - 核心数据合并策略有时会删除对象
问题描述
我正在尝试创建简单的应用程序,该应用程序在拆分视图的左侧有对话,在右侧有消息。左侧的会话列表将显示每个会话的记录 ID、描述和最新消息的详细信息。消息视图有一个“添加消息”按钮,它将模拟向对话中添加消息。这将始终使用相同的消息 ID。这是为了测试合并策略,它是一个 NSMergeByPropertyObjectTrumpMergePolicy。我面临的问题是,在您添加新消息的时间里,latestMessage 变为 nil 50%。这将导致应用程序崩溃,因为会话列表正在使用 latestMessage 属性来显示 latestMessage 详细信息。
我正在寻找一种解决方案,以解决如何有效地在对话列表中显示 latestMessage 而不会导致崩溃。我还想首先了解导致此问题的原因。我的假设是它与合并策略有关,但我不确定为什么,因为我的所有其他对象都毫无问题地尊重合并策略。
所有必要的代码都在下面。如果您想下载它,这里是该项目的链接。
struct ContentView: View {
var body: some View {
NavigationView{
ConversationsView()
Text("Select a conversation")
}
}
}
struct MessagesView: View{
@StateObject var model: MessagesModel
@ObservedObject var conversation: Conversation
init(conversation: Conversation) {
_model = StateObject(wrappedValue: MessagesModel(conversation: conversation))
self.conversation = conversation
}
var body: some View{
List(model.messages){ message in
VStack(alignment: .leading){
Text("ID: \(message.id)")
Text("Message: \(message.message)")
Text("Person: \(message.person.firstName)")
}
}
.navigationTitle("Messages \(model.messages.count)")
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing){
Button("Add Messages"){
model.addMessage(to: conversation)
}
}
})
}
}
struct ConversationsView: View{
@StateObject var model = ConversationModel()
var body: some View{
List(model.conversations){ convo in
NavigationLink(
destination: MessagesView(conversation: convo),
label: {
VStack(alignment: .leading){
Text("Record id: \(convo.id)")
.font(.headline)
Text("Description: \(convo.myDescription)")
.font(.headline)
Text("\(convo.latestMessage.person.fullName): \(convo.latestMessage.message)")
.font(.subheadline)
}
})
}.navigationTitle("Conversations \(model.conversations.count)")
}
}
class ConversationModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate{
@Published var conversations = [Conversation]()
private let conversationsController: NSFetchedResultsController<Conversation>
override init(){
conversationsController = NSFetchedResultsController(fetchRequest: Conversation.getAllConversations(),
managedObjectContext: PersistentStore.shared.context,
sectionNameKeyPath: nil,
cacheName: nil)
super.init()
conversationsController.delegate = self
do {
try conversationsController.performFetch()
conversations = conversationsController.fetchedObjects ?? []
createConversations()
} catch {
print("Error: \(error.localizedDescription)")
}
}
private func createConversations(){
let context = PersistentStore.shared.context
let conversation = Conversation(context: context)
let ricky = Person(context: context)
let message = Message(context: context)
ricky.firstName = "Ricky"
ricky.lastName = "W"
ricky.title = "iOS Dev"
message.id = 1
message.message = "Testing 123"
message.timeStamp = Date().timeIntervalSince1970
message.person = ricky
conversation.myDescription = "This is a conversation"
conversation.id = 1
conversation.activeParticipants.insert(ricky)
conversation.latestMessage = message
conversation.messages.insert(message)
PersistentStore.shared.saveContext()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
if let items = controller.fetchedObjects as? [Conversation]{
conversations = items
}
}
}
class MessagesModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate{
@Published var messages = [Message]()
private let messagesController: NSFetchedResultsController<Message>
init(conversation: Conversation){
messagesController = NSFetchedResultsController(fetchRequest: Message.getMessagesFor(conversation),
managedObjectContext: PersistentStore.shared.context,
sectionNameKeyPath: nil,
cacheName: nil)
super.init()
messagesController.delegate = self
do {
try messagesController.performFetch()
messages = messagesController.fetchedObjects ?? []
} catch {
print("Error: \(error.localizedDescription)")
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
if let items = controller.fetchedObjects as? [Message]{
messages = items
}
}
func addMessage(to conversation: Conversation){
let context = PersistentStore.shared.context
let tim = Person(context: context)
tim.firstName = "Tim"
tim.lastName = "Apple"
tim.title = "CEO"
let message = Message(context: context)
message.id = 2
message.message = "I Am Tim Apple"
message.timeStamp = Date().timeIntervalSince1970
message.person = tim
conversation.messages.insert(message)
conversation.latestMessage = message
PersistentStore.shared.saveContext()
}
}
class PersistentStore: ObservableObject {
var context: NSManagedObjectContext {
persistentContainer.viewContext
}
static let shared = PersistentStore()
private init() {}
// MARK: - Core Data stack
let persistentContainer: NSPersistentContainer = {
let container = NSPersistentCloudKitContainer(name: "CoreDataTesting")
// Enable remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
// MARK: - Delete For Debug
func deleteEverythingForDebug(){
let conversationsRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Conversation")
let messagesRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Message")
let personRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Person")
let conversationsDeleteRequest = NSBatchDeleteRequest(fetchRequest: conversationsRequest)
let messagesDeleteRequest = NSBatchDeleteRequest(fetchRequest: messagesRequest)
let personDeleteRequest = NSBatchDeleteRequest(fetchRequest: personRequest)
do {
try persistentContainer.viewContext.execute(conversationsDeleteRequest)
try persistentContainer.viewContext.execute(messagesDeleteRequest)
try persistentContainer.viewContext.execute(personDeleteRequest)
print("Deleted all of Core Data objects.")
} catch {
fatalError("Error deleting everything: \(error)")
}
saveContext()
}
}
public class Conversation : NSManagedObject, Identifiable{
@NSManaged public var activeParticipants : Set<Person>
@NSManaged public var myDescription : String
@NSManaged public var id : Int
@NSManaged public var messages : Set<Message>
@NSManaged public var latestMessage : Message
static func getAllConversations() -> NSFetchRequest<Conversation> {
let request : NSFetchRequest<Conversation> = Conversation.fetchRequest() as! NSFetchRequest<Conversation>
request.sortDescriptors = [NSSortDescriptor(key: "latestMessage.timeStamp", ascending: false)]
return request
}
}
public class Message : NSManagedObject, Identifiable {
@NSManaged public var id : Int
@NSManaged public var message : String
@NSManaged public var timeStamp : Double
@NSManaged public var person : Person
@NSManaged public var conversation : Conversation
@NSManaged public var latestConversation : Conversation
static func getMessagesFor(_ conversation: Conversation) -> NSFetchRequest<Message> {
let request : NSFetchRequest<Message> = Message.fetchRequest() as! NSFetchRequest<Message>
request.sortDescriptors = [NSSortDescriptor(key: "timeStamp", ascending: true)]
request.predicate = NSPredicate(format: "conversation == %@", conversation)
return request
}
static func getAllMessages() -> NSFetchRequest<Message> {
let request : NSFetchRequest<Message> = Message.fetchRequest() as! NSFetchRequest<Message>
request.sortDescriptors = [NSSortDescriptor(key: "timeStamp", ascending: true)]
return request
}
}
public class Person: NSManagedObject, Identifiable {
@NSManaged public var firstName: String
@NSManaged public var lastName: String
@NSManaged public var title: String
@NSManaged public var conversations: Set<Conversation>
@NSManaged public var messages: Set<Message>
var fullName : String{
"\(firstName) \(lastName)"
}
static func getAllPeopleWith(title: String) -> NSFetchRequest<Person> {
let request : NSFetchRequest<Person> = Person.fetchRequest() as! NSFetchRequest<Person>
request.sortDescriptors = [NSSortDescriptor(key: "firstName", ascending: false)]
request.predicate = NSPredicate(format: "title == %@", title)
return request
}
static func getAllPeople() -> NSFetchRequest<Person> {
let request : NSFetchRequest<Person> = Person.fetchRequest() as! NSFetchRequest<Person>
request.sortDescriptors = [NSSortDescriptor(key: "firstName", ascending: false)]
return request
}
}
解决方案
推荐阅读
- java - TypeMappingException:检测到具有不同返回类型的操作“opName”的多个方法
- docker - 将 CI 容器中的卷挂载到 docker-compose 容器中
- android - 在 rxjava 中订阅消费者?
- api-design - 如何在 Qlik Sense 中创建一个触发成功任务的任务
- android - 找不到 com.android.tools.build:gradle:3.6.2
- excel - 根据标题和前列设置单元格范围值
- python - Pyomo:使用两个索引初始化变量
- puppeteer - Puppeteersharp 错误:导航失败,因为浏览器已断开连接
- javascript - 如何使用缩放之类的方法减少多行文本?
- azure-devops - 如何设置包含此格式日期的 Azure DevOps Pipeline 变量:25.07.2020