Swift 核心数据:使用环境变量managedObjectContext时出现问题

Swift 核心数据:使用环境变量managedObjectContext时出现问题,swift,core-data,swiftui,nsmanagedobjectcontext,nspersistentcontainer,Swift,Core Data,Swiftui,Nsmanagedobjectcontext,Nspersistentcontainer,我是swift的新手,在理解环境变量如何工作方面有困难 在核心数据中,我创建了一个名为“API”的新实体,它有一个属性id:Int32 然后在SwiftUI中,我想找到id的最大值。我写了一个请求,但每当我使用passed作为环境变量managedObjectContext查看时,它总是会使我的应用程序/预览崩溃。以下是使用NSManagedObjectContext.fetch(NSFetchRequest)后的崩溃信息(使用FetchRequest仅提供stacktrace,异常为EXC_B

我是swift的新手,在理解环境变量如何工作方面有困难

在核心数据中,我创建了一个名为“API”的新实体,它有一个属性id:Int32

然后在SwiftUI中,我想找到id的最大值。我写了一个请求,但每当我使用passed作为环境变量managedObjectContext查看时,它总是会使我的应用程序/预览崩溃。以下是使用NSManagedObjectContext.fetch(NSFetchRequest)后的崩溃信息(使用FetchRequest仅提供stacktrace,异常为EXC_BAD_指令)

请记住,此错误会随着我使用的项目的不同而变化。在我的主要项目中,我犯了这样的错误:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'WebsiteAPI''
这是我正在使用的代码

import SwiftUI
import CoreData

struct test: View {
    private var id: Int32
    @Environment(\.managedObjectContext) var managedObjectContext

    var body: some View {
        Text("id=\(id)")
    }

    public init(context: NSManagedObjectContext) {
        self.id = -1

        //this crashes and gives no usefull information
//        let request2 = FetchRequest<API>(
//            entity: API.entity(),
//            sortDescriptors: [NSSortDescriptor(keyPath: \API.id, ascending: false)]
//        )
//        self.id = request2.wrappedValue.first?.id ?? 1

        guard let context2 = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
            fatalError("Unable to read managed object context.")
        }

        let request = NSFetchRequest<API>(entityName: "API")
        request.sortDescriptors = [NSSortDescriptor(keyPath: \API.id, ascending: false)]
        do {
            var commits = try context.fetch(request)   // OK
            commits = try context2.fetch(request)  // OK
            //commits = try self.managedObjectContext.fetch(request)  // causing crash
            self.id = Int32(commits.count)
        } catch let error {
            print(error.localizedDescription)
        }
    }
}

struct test_Previews: PreviewProvider {
    static var previews: some View {
        guard let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
            fatalError("Unable to read managed object context.")
        }
        return test(context: context).environment(\.managedObjectContext, context)
    }
}
导入快捷界面
导入CoreData
结构测试:视图{
私有变量id:Int32
@环境(\.managedObjectContext)变量managedObjectContext
var body:一些观点{
文本(“id=\(id)”)
}
公共初始化(上下文:NSManagedObjectContext){
self.id=-1
//这会崩溃,并且不会提供任何有用的信息
//让request2=FetchRequest(
//实体:API.entity(),
//sortDescriptors:[NSSortDescriptor(密钥路径:\API.id,升序:false)]
//        )
//self.id=request2.wrappedValue.first?.id±1
guard let context2=(UIApplication.shared.delegate作为?AppDelegate)?.persistentContainer.viewContext else{
fatalError(“无法读取托管对象上下文”)
}
let request=NSFetchRequest(entityName:“API”)
request.sortDescriptors=[NSSortDescriptor(keyPath:\API.id,升序:false)]
做{
var commits=try context.fetch(请求)//确定
提交=尝试context2.fetch(请求)//确定
//提交=try self.managedObjectContext.fetch(请求)//导致崩溃
self.id=Int32(commits.count)
}捕捉错误{
打印(错误。本地化描述)
}
}
}
结构测试预览:PreviewProvider{
静态var预览:一些视图{
guard let context=(UIApplication.shared.delegate作为?AppDelegate)?.persistentContainer.viewContext else{
fatalError(“无法读取托管对象上下文”)
}
返回测试(上下文:context).environment(\.managedObjectContext,context)
}
}
所有评论行崩溃应用程序。为什么从AppDelegate.persistentContainer.viewContext获取上下文效果很好,但使用环境变量managedObjectContext(我认为应该是相同的)却不起作用?我花了5个小时在这上面,检查了几乎所有的东西,尝试了很多东西,但没有成功。最后,我可以继续从AppDelegate获取上下文,但环境变量有什么问题吗?我是缺少一些常识还是只是一个bug?我在Xcode中遇到的错误让我头疼,从清除构建文件夹后缺少自动完成,到更改所有引用的结构/文件名后出现数百个错误,尽管之后成功构建了。每天重新启动Xcode几次以使其正常工作对我来说是正常的

还有一些我注意到的事情,当我创建FetchRequest作为一个变量并将其用于正文中的一些列表时,它工作了。问题只是,当我尝试在代码/函数/初始化中手动获取东西时,比如按钮操作或appear、init等方法。我尝试在物理设备上运行应用程序并显示预览。同样的效果


我在Swift 5中使用的是Xcode 11.4。

像SwiftUI中的
视图
这样的结构是值类型,不能以正常方式初始化任何对象,因为视图结构只在状态更改期间创建,然后就消失了。因此,它们创建的任何对象都会立即丢失。例如,在init方法中,您创建了
NSFetchRequest
NSSortDescriptor
以及所有获取的对象。视图结构通常在每次状态更改和父实体运行时都是init,因此您将创建数千个堆对象,这些对象将填满内存并使SwiftUI缓慢爬行。这些问题可以在Instruments->SwiftUI“跟踪分析.视图类型主体调用”中诊断

显然,我们确实需要创建对象,所以这就是属性包装器的用武之地。通过使用属性包装器作为对象分配的前缀,然后以一种特殊的方式创建对象,在这种方式中,对象只初始化一次,并且每次重新创建新结构时,都会将相同的实例提供给它。正如我所说的,在SwiftUI中经常发生这种情况,频率的高低取决于您在组织视图结构层次结构方面所付出的努力。运行状况警告:目前在线提供的大多数示例代码对此不做任何努力,并且不必要地更新大量视图层次结构,因为设计人员将其视图结构设计为视图控制器,而不是使其尽可能小,并且只具有在主体中实际使用的属性

为了解决您的问题,您需要使用属性包装器
@StateObject
来安全地初始化您的对象,并且它必须符合
observeObject,以便可以通知SwiftUI对象将要更改,以便在所有对象通知后,它可以调用肯定需要的body,除非开发人员没有在他们的主体中使用对象,否则代码编写得很糟糕。在调用视图主体之前创建一次对象,然后每次重新创建
视图
时,都会为其提供现有对象,而不是创建新对象。当视图不再显示时,它将自动取消显示。使用
onAppear
在视图第一次出现时配置对象,使用
onChange
更新对象。Fust有一个func
fetch
,它提供
managedObjectContext
和您的
id
fetch参数,并在其中创建一个
NSFetchedResultsController
执行提取
import SwiftUI
import CoreData

struct test: View {
    private var id: Int32
    @Environment(\.managedObjectContext) var managedObjectContext

    var body: some View {
        Text("id=\(id)")
    }

    public init(context: NSManagedObjectContext) {
        self.id = -1

        //this crashes and gives no usefull information
//        let request2 = FetchRequest<API>(
//            entity: API.entity(),
//            sortDescriptors: [NSSortDescriptor(keyPath: \API.id, ascending: false)]
//        )
//        self.id = request2.wrappedValue.first?.id ?? 1

        guard let context2 = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
            fatalError("Unable to read managed object context.")
        }

        let request = NSFetchRequest<API>(entityName: "API")
        request.sortDescriptors = [NSSortDescriptor(keyPath: \API.id, ascending: false)]
        do {
            var commits = try context.fetch(request)   // OK
            commits = try context2.fetch(request)  // OK
            //commits = try self.managedObjectContext.fetch(request)  // causing crash
            self.id = Int32(commits.count)
        } catch let error {
            print(error.localizedDescription)
        }
    }
}

struct test_Previews: PreviewProvider {
    static var previews: some View {
        guard let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
            fatalError("Unable to read managed object context.")
        }
        return test(context: context).environment(\.managedObjectContext, context)
    }
}
import SwiftUI
import CoreData

struct ContentView: View {
    
    var body: some View {
        NavigationView {
            MasterView(name:"Master")
                .navigationTitle("Master")
        }
    }
}


class ItemsFetcher : NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
    var managedObjectContext : NSManagedObjectContext?
    
    @Published
    private(set) var items : Array<Item> = []

    lazy var fetchedResultsController : NSFetchedResultsController<Item> = {
        let frc = NSFetchedResultsController<Item>(fetchRequest: Item.myFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
        frc.delegate = self
        return frc
    }()
    
    func fetch(name:String, ascending: Bool){
        fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "name = %@", name)
        fetchedResultsController.fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: ascending)]
        try! fetchedResultsController.performFetch()
        items = fetchedResultsController.fetchedObjects ?? []
    }
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        objectWillChange.send()
        items = fetchedResultsController.fetchedObjects ?? []
    }
}

struct MasterView: View {
    @Environment(\.managedObjectContext) private var viewContext
    let name: String
    @State
    var ascending = false
    
    @StateObject private var itemsFetcher = ItemsFetcher()
    
    var body: some View {
        List {
            ForEach(itemsFetcher.items) { item in
                Text("Item at \(item.timestamp!, formatter: itemFormatter)")
            }
            .onDelete(perform: deleteItems)
        }
        .toolbar {
            #if os(iOS)
            ToolbarItem(placement: .navigation){
                EditButton()
            }
            #endif
            ToolbarItem(placement: .automatic){
                Button(action: addItem) {
                    Label("Add Item", systemImage: "plus")
                }
            }
            ToolbarItem(placement: .bottomBar){
                Button(action: {
                    ascending.toggle()
                }) {
                    Text(ascending ? "Descending" : "Ascending")
                }
            }
        }
        .onAppear() {
            itemsFetcher.managedObjectContext = viewContext
            fetch()
        }
        .onChange(of: ascending) { newValue in
            fetch()
        }
        
    }
    
    func fetch(){
        itemsFetcher.fetch(name: name, ascending: ascending)
    }
    
    private func addItem() {
        
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            newItem.name = "Master"

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map {itemsFetcher.items[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}