iOS 13.4 CoreData SwiftUI app crashes with “EXC_BREAKPOINT (code=1, subcode=0x1f3751f08)” on device

杀马特。学长 韩版系。学妹 提交于 2020-04-14 04:46:28

问题


A very simple CoreData app: All code provided below.

  • Start up with CoreData template single view app.
  • 2 entities with a string attribute each: Message(title) and Post(name)

A NavigationView containing

  • NavigationLink to a list of messages
  • NavigationLink to a list of posts

Each linked ListView (Message/Post) has

  • a button to add an item to the list
  • a button to remove all items from the list

Now, when you run this app on a simulator (any iOS 13.x version) all runs as expected from the description above.

But on a DEVICE running iOS 13.4

  • Tap "Messages"
  • Creating/deleting messages works fine, SwiftUi view updates immediately.
  • Tap "back"
  • Tap "Messages" again. While still creating/deleting messages works fine: The debugger now shows a warning: "Context in environment is not connected to a persistent store coordinator: NSManagedObjectContext: 0x280ed72c0
  • Tap "Posts" ==> App crashes with EXC_BREAKPOINT (code=1, subcode=0x1f3751f08)

You can start the process with Posts first, too. Then the same crash occurs on the messages list view.

I strongly believe this is an iOS 13.4 bug because similar code ran fine on Xcode 11.3 / iOS 13.3.

Does anyone know a fix or workaround for this?

Here is a link to the full project: Full Xcode Project

The ContentView:

import SwiftUI
import CoreData


struct MessageList: View {
  @Environment(\.managedObjectContext) var moc
  @FetchRequest(entity: Message.entity(), sortDescriptors: [])
  var messages: FetchedResults<Message>

  var body: some View {
    List() {
      ForEach(messages, id: \.self) { message in
        Text(message.title ?? "?")
      }
    }
    .navigationBarItems(trailing:
      HStack(spacing: 16) {
        Button(action: deleteMessages) {
          Image(systemName: "text.badge.minus")
        }
        Button(action: addMessage) {
          Image(systemName: "plus.app")
        }
      }
    )
  }
  func addMessage() {
    let m = Message(context: moc)
    m.title = "Message: \(Date())"
    try! moc.save()
  }
  func deleteMessages() {
    messages.forEach {
      moc.delete($0)
    }
  }
}

struct PostList: View {
  @Environment(\.managedObjectContext) var moc
  @FetchRequest(entity: Post.entity(), sortDescriptors: [])
  var posts: FetchedResults<Post>

  var body: some View {
    List {
      ForEach(0..<posts.count, id: \.self) { post in
        Text(self.posts[post].name ?? "?")
      }
    }
    .navigationBarItems(trailing:
      HStack(spacing: 16) {
        Button(action: deletePosts) {
          Image(systemName: "text.badge.minus")
        }
        Button(action: addPost) {
          Image(systemName: "plus.app")
        }
      }
    )
  }
  func addPost() {
    let p = Post(context: moc)
    p.name = "Post \(UUID().uuidString)"
    try! moc.save()
  }
  func deletePosts() {
    posts.forEach {
      moc.delete($0)
    }
    try! moc.save()
  }
}


struct ContentView: View {
  @Environment(\.managedObjectContext) var moc

  var body: some View {
    NavigationView {
      VStack(alignment: .leading){
        NavigationLink(destination: MessageList()) {
          Text("Messages")
        }.padding()
        NavigationLink(destination: PostList()) {
          Text("Posts")
        }.padding()
        Spacer()
      }
    }.navigationViewStyle(StackNavigationViewStyle())
  }
}

struct ContentView_Previews: PreviewProvider {
  static let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
  static var previews: some View {
    ContentView()
      .environment(\.managedObjectContext, moc)
  }
}

Screenshot of the Model:

The SceneDelegate (unaltered from template, provided for completeness):

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let contentView = ContentView().environment(\.managedObjectContext, context)

    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
  }

  func sceneDidDisconnect(_ scene: UIScene) {}
  func sceneDidBecomeActive(_ scene: UIScene) {}
  func sceneWillResignActive(_ scene: UIScene) {}
  func sceneWillEnterForeground(_ scene: UIScene) {}
  func sceneDidEnterBackground(_ scene: UIScene) {
    (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
  }
}

The AppDelegate (unaltered from template, provided for completeness):

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    return true
  }

  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  }

  func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}

  // MARK: - Core Data stack

  lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "Coredata134")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    return container
  }()

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

回答1:


I also believe this is a bug.

You can workaround for now by setting the environment variable again within the NavigationLinks in ContentView:

NavigationLink(destination: MessageList().environment(\.managedObjectContext, moc)) {
      Text("Messages")
    }.padding()
NavigationLink(destination: PostList().environment(\.managedObjectContext, moc)) {
      Text("Posts")
    }.padding()

EDIT:

Just noticed that this workaround has at least one serious negative side effect: in case the @FetchRequest in the destination View uses a sortDescriptor and the destination View itself contains a NavigationLink, (e.g. to a DetailView), then modifying an attribute contained in the sortDescriptor in the DetailView will cause the DetailView to be popped and pushed again as soon as the new attribute value leads to a new sort order.

To demonstrate this:

a) add a new attribute of type Integer 16 named "value" to the Message entity in the Core Data model.

b) update func addMessage() as follows:

func addMessage() {
    let m = Message(context: moc)
    m.title = "Message: \(Date())"
    m.value = 0
    try! moc.save()
}

c) add the following struct to ContentView.swift

struct MessageDetailList: View {
    @ObservedObject var message: Message
    var body: some View {
        Button(action: {
            self.message.value += 1
        }) {
            Text("\(message.title ?? "?"): value = \(message.value)")
        }
    }
}

d) Update the ForEach in struct MessageList as follows:

ForEach(messages, id: \.self) { message in
        NavigationLink(destination: MessageDetailList(message: message).environment(\.managedObjectContext, self.moc)) {
            Text("\(message.title ?? "?"): value = \(message.value)")
        }
    }

e) replace @FetchRequest in MessageList with:

@FetchRequest(entity: Message.entity(), sortDescriptors: [NSSortDescriptor(key: "value", ascending: false)])

Run the code and tap on "Messages". Create three messages, then tap on the third one. In the DetailView, tap on the Button. This will increase the value attribute of this message to 1 and thus resort the fetch results on MessageList, which will trigger a pop and push again of the detail list.



来源:https://stackoverflow.com/questions/60843114/ios-13-4-coredata-swiftui-app-crashes-with-exc-breakpoint-code-1-subcode-0x1f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!