[英]SwiftUI App Crashing EXC_BAD_ACCESS error when coredata list items are deleted
以下代碼一直有效,直到對列表項執行刪除操作。 但這並不是每次代碼運行時都會發生的。 它因線程錯誤的EXC_BAD_ACCESS
代碼而崩潰:
然后我意識到,如果應用程序保持運行,則在刪除操作后一段時間后會發生此錯誤
這就是為什么這個錯誤很難弄清楚的原因。 但是當我將DispatchQueue.main.async
方法添加到Model.swift
中的tasks
時,它開始出現。
此代碼的目的是當列表中發生任何更改時,使用self.fetchAll()
方法從核心數據中重新加載更新的結果。
我注意到的另一個問題是刪除后出現的紅線。
問題:
macOS:10.15.4
XCode:11.5
目標:10.15
Model.swift
import Foundation
import CoreData
class Model: ObservableObject {
@Published var context: NSManagedObjectContext
@Published var tasks: [Task] = [Task]() {
didSet {
DispatchQueue.main.async {
self.fetchAll()
}
}
}
init(_ viewContext: NSManagedObjectContext) {
context = viewContext
}
}
// MARK: Methods
extension Model {
func fetchAll() {
let req = Task.fetchRequest() as NSFetchRequest<Task>
req.sortDescriptors = [NSSortDescriptor(keyPath: \Task.name, ascending: true)]
do {
self.tasks = try self.context.fetch(req)
} catch let error as NSError {
print(error.localizedDescription)
}
}
func addTask(_ text: String) {
let name = text.trimmingCharacters(in: .whitespacesAndNewlines)
if (name != "") {
let task = Task(context: self.context)
task.id = UUID()
task.name = name
task.creationTimestamp = Date()
task.updatedTimestamp = Date()
self.context.insert(task)
self.save()
}
}
func save() {
guard self.context.hasChanges else { return }
do {
try self.context.save()
print("Saved changes")
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
AppDelegate.swift
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var model: Model!
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
model = Model(persistentContainer.viewContext)
let contentView = ContentView().environmentObject(model)
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) { }
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Todo_List")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: - Core Data Saving and Undo support
@IBAction func saveAction(_ sender: AnyObject?) {
let context = persistentContainer.viewContext
if !context.commitEditing() {
NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving")
}
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
NSApplication.shared.presentError(nserror)
}
}
}
func windowWillReturnUndoManager(window: NSWindow) -> UndoManager? {
return persistentContainer.viewContext.undoManager
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
let context = persistentContainer.viewContext
if !context.commitEditing() {
NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate")
return .terminateCancel
}
if !context.hasChanges {
return .terminateNow
}
do {
try context.save()
} catch {
let nserror = error as NSError
let result = sender.presentError(nserror)
if (result) {
return .terminateCancel
}
let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message")
let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info");
let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title")
let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title")
let alert = NSAlert()
alert.messageText = question
alert.informativeText = info
alert.addButton(withTitle: quitButton)
alert.addButton(withTitle: cancelButton)
let answer = alert.runModal()
if answer == .alertSecondButtonReturn {
return .terminateCancel
}
}
return .terminateNow
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var model: Model
@State var taskName: String = ""
var body: some View {
VStack{
ZStack{
TaskList()
.padding(.top,31)
VStack(spacing:0){
TextField("New Task", text: self.$taskName, onCommit: {
self.model.addTask(self.taskName)
self.taskName = ""
})
.padding(5)
Divider().offset(y:-1)
Spacer()
}
if model.tasks.isEmpty {
Text("Nothing To Do\nPlease add something To Do.")
.multilineTextAlignment(.center)
.font(.headline)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
.frame(minWidth: 200, maxWidth: .infinity, maxHeight: .infinity)
.onAppear{
self.model.fetchAll()
}
}
}
任務列表.swift
import SwiftUI
struct TaskList: View {
@EnvironmentObject var model: Model
@State var selection: Task?
var body: some View {
List(selection: self.$selection){
ForEach(self.model.tasks, id: \.self) { task in
TaskRow(task: task).tag(task)
}
.onDelete(perform: onDelete)
}
}
private func onDelete(with indexSet: IndexSet) {
indexSet.forEach { index in
let task = self.model.tasks[index]
self.model.context.delete(task)
}
self.model.save()
}
}
TaskRow.swift
struct TaskRow: View {
@ObservedObject var task: Task
@EnvironmentObject var model: Model
var dateString: String {
if let timestamp = task.updatedTimestamp {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .medium
return formatter.string(from: timestamp)
} else {
return "No date recorded"
}
}
var body: some View {
HStack {
Toggle(isOn: Binding<Bool>(get: { () -> Bool in
return self.task.checked
}, set: { (enabled) in
self.task.checked = enabled
self.task.updatedTimestamp = Date()
self.model.save()
})){
Text("")
}
VStack {
HStack {
Text(task.name ?? "Unknown Task").font(.system(size: 20))
Spacer()
}
HStack {
Text(dateString).font(.system(size: 10)).bold()
Spacer()
}
}
}
}
}
確保Task
是可識別的:
extension Task: Identifiable {
}
如果你不知道你應該能夠在你的ForEach
中做到這一點:
ForEach(self.model.tasks) {task in
}
我其實也遇到過這個問題。 我必須做的解決方法是添加一個字段isDeleted
而不是實際刪除 CoreData 中的記錄。 然后,您可以稍后在應用程序處於后台或終止時清理它。 或者只是將記錄保存在 Coredata 中。
由於我最近有類似的問題可以解決,我想我會試試你的例子。 我不確定您的問題是否仍然存在,因為該問題已經 5 個月大而沒有得到回答。
簡而言之:是的,您可以修復它。 但是您必須使用 SwiftUI 而不是反對它。 這意味着:您的 model 可以提供獲取請求,但您不應該自己執行獲取。 讓 SwiftUI 為您處理這個問題。
以下是所需的更改:
創建 ContentView 時,您應該將托管的 object 上下文添加到 SwiftUI 環境中:
let contentView = ContentView()
.environmentObject(model)
.environment(\.managedObjectContext, persistentContainer.viewContext)
刪除您的tasks
發布屬性和fetchAll
function。 我們將讓 SwiftUI 處理這個問題。
刪除對model.fetchAll()
的調用和對model.tasks.isEmpty
的檢查,因為 ContentView 對任務一無所知。
添加FetchRequest
以創建您的tasks
屬性並在此處添加對tasks.isEmpty
的檢查。 這是代碼:
struct TaskList: View {
@EnvironmentObject var model: Model
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)],
animation: .default)
private var tasks: FetchedResults<Task>
@State var selection: Task?
@ViewBuilder
var body: some View {
if tasks.isEmpty {
Text("Nothing To Do\nPlease add something To Do.")
.multilineTextAlignment(.center)
.font(.headline)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
else {
List(selection: $selection){
ForEach(tasks, id: \.id) { task in
TaskRow(task: task).tag(task)
}
.onDelete(perform: onDelete)
}
}
}
private func onDelete(with indexSet: IndexSet) {
indexSet.forEach { index in
let task = tasks[index]
self.model.context.delete(task)
}
self.model.save()
}
}
使用這種方法確實對我很有效。 此外,刪除 animation 現在正在正常工作和繪圖。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.