在CoreData中保存大量数据时出现问题。

huangapple go评论65阅读模式
英文:

Trouble while saving large volume of data in coredata

问题

以下是您要翻译的内容:

"I was trying to save a large volume of data in core data using async/await. The below function works fine for fewer records (5-10K), but when I push the limits and stress test the setup for more than 10K records I get errors on the random lines of code. Below are a few errors while saving the data.

  1. Thread 1: EXC_BREAKPOINT (code=1, subcode=0x18a927e88)
  2. value optimized out. Value is correctly passed in the function but for some random records gives this error
  3. Gets an error while closing the app that triggers from applicationShouldTerminate function in appdelegate

I run the below code in a loop to add records in core data --

    @NSManaged public var categoryName: String
    @NSManaged public var categoryIcon: Data?
    @NSManaged public var categorySearchKey: String
    @NSManaged public var person: NSSet?
    @NSManaged public var country: NSSet
    

    class func addCategory(categoryName: String, categoryIcon: NSImage?, country: [Country], persons: [Person]?) async {
        let appDelegate = await NSApplication.shared.delegate as! AppDelegate
        let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Category", in: context)
        let CD = Category(entity: entity!, insertInto: context)
        CD.categoryName = categoryName
        CD.country = NSSet(array: country)
        if let persons = persons {
            CD.person = NSSet(array: persons)
        }
        if let categoryIcon = categoryIcon {
            CD.categoryIcon = categoryIcon.tiffRepresentation!
        }
        var s = [String]()
        s.append(categoryName)
        s.append(country.map({$0.countryName}).joined(separator: ", "))
        if persons != nil {
            s.append(persons.map({$0.personName}).joined(separator: ", "))
        }
        CD.categorySearchKey = s.joined(separator: ", ")
    }

After adding the data, below code is used to save core data --

        func saveCoreData() async {
            let appDelegate = await NSApplication.shared.delegate as! AppDelegate
            let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
            do {
                try await MainActor.run(body: {
                    try context.save()
                })
            }
            catch {
                print("Error in saving core data")
            }
        }
    }

I have tried saving without the main actor as well, same result, random errors. Also, I have tried .newBackgroundContext() instead of .viewContext while saving the data, same random errors.

Because the errors are random, I cannot pinpoint the root cause to fix. Any guidance is appreciated. Thank you."

英文:

I was trying to save a large volume of data in core data using async/await. The below function works fine for fewer records (5-10K), but when I push the limits and stress test the setup for more than 10K records I get errors on the random lines of code. Below are a few errors while saving the data.

  1. Thread 1: EXC_BREAKPOINT (code=1, subcode=0x18a927e88)
  2. value optimized out. Value is correctly passed in the function but for some random records gives this error
  3. Gets an error while closing the app that triggers from applicationShouldTerminate function in appdelegate

I run the below code in a loop to add records in core data --

    @NSManaged public var categoryName: String
    @NSManaged public var categoryIcon: Data?
    @NSManaged public var categorySearchKey: String
    @NSManaged public var person: NSSet?
    @NSManaged public var country: NSSet
    

    class func addCategory(categoryName : String, categoryIcon: NSImage?, country : [Country], persons : [Person]?)async{
        let appDelegate = await NSApplication.shared.delegate as! AppDelegate
        let context : NSManagedObjectContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Category", in: context)
        let CD = Category(entity: entity!, insertInto: context)
        CD.categoryName = categoryName
        CD.country = NSSet(array: country)
        if let persons = persons{
            CD.person = NSSet(array: persons)
        }
        if let categoryIcon = categoryIcon{
            CD.categoryIcon = categoryIcon.tiffRepresentation!
        }
        var s = [String]()
        s.append(categoryName)
        s.append(country.map({$0.countryName}).joined(separator: ", "))
        if let persons = persons {
            s.append(persons.map({$0.personName}).joined(separator: ", "))
        }
        CD.categorySearchKey = s.joined(separator: ", ")
    }

After adding the data, below code is used to save core data --

        func saveCoreData()async{
            let appDelegate = await NSApplication.shared.delegate as! AppDelegate
            let context : NSManagedObjectContext = appDelegate.persistentContainer.viewContext
            do {
                try await MainActor.run(body: {
                    try context.save()
                })
            }
            catch {
                print("Error in saving core data")
            }
        }
    }

I have tried saving without the main actor as well, same result, random errors. Also I have tried .newBackgroundContext() instead of .viewContext while saving the data, same random errors.

Because the errors are random, I cannot pinpoint the root cause to fix. Any guidance is appreciated. Thank you.

答案1

得分: 1

如果您正在添加大量数据并且不使用批量插入,您需要定期保存更改,而不是等待过程结束。您新插入的所有对象都保存在内存中,内存不断增加,直到保存操作执行。如果您在Xcode中监视应用程序的内存使用情况,您可能会注意到这一点。

您可以通过定期保存更改来控制这一点 - 每隔100次、每隔500次或其他间隔。间隔取决于每个实例包含多少数据,因此这取决于您的应用程序。尝试一些实验以找出哪种方式效果最好。

英文:

If you're adding a lot of data and not using batch inserts, you need to save changes from time to time, instead of waiting for the end of the process. All of your newly inserted objects are in memory, which keeps on growing, until you save. You could probably see this if you monitor your app's memory use in Xcode while it runs.

You can control this by saving changes at regular intervals-- every 100, or every 500, or some other interval. The interval depends on how much data each instance contains, so it depends on your app. Experiment a little to see what works best.

答案2

得分: 0

你说控制台中有许多随机错误,为什么不尝试创建一个Singleton类来管理数据库呢?你可以创建类似这样的内容:

class DataManager {

    static let shared = DataManager()

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Category") // Your Database Name
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // 从数据库中获取数据
    func fetchData() -> [Category] {
        let fetchRequest: NSFetchRequest<Category> = Category.fetchRequest()
        do {
            let result = try persistentContainer.viewContext.fetch(fetchRequest)
            return result
        } catch {
            print("Failed to fetch data: \(error)")
            return []
        }
    }

    // 保存数据
    func saveData() {
        let context = persistentContainer.viewContext
        if context hasChanges {
            do {
                try context.save()
            } catch {
                print("Failed to save data: \(error)")
            }
        }
    }

    // 将数据项添加到数据库中
    func addData(categoryName: String, categoryIcon: Data?, categorySearchKey: String, person: NSSet?, country: NSSet) {
        let context = persistentContainer.viewContext
        let entity = Category(context: context)
        entity.categoryName = categoryName
        if let icon = categoryIcon {
           entity.categoryIcon = icon
        }
        entity.categorySearchKey = categorySearchKey
        if let categoryPerson = person {
           entity.person = categoryPerson
        }
        entity.country = country

        saveData()
    }

    // 删除数据
    func deleteAllData() {
        let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Category")
        let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)

        do {
            try persistentContainer.viewContext.execute(deleteRequest)
            try persistentContainer.viewContext.save()
        } catch {
            print("Failed to delete data: \(error)")
        }
    }
    
}

然后只需调用 DataManager.shared.addData(...)

这只是一个简单的单例类来管理数据库。你可能需要根据你的数据库属性进行修改。我只是根据你的属性添加了这些方法,所以请根据你的需求自由修改 addData() 方法,然后会自动调用 save()。以上的代码将添加包含 categoryName、person 等内容的单个实体。你可以根据需要不断添加它们,然后像这样检索它们:let categoriesArray = DataManager.shared.fetchData(),你将获得 [Category] 数组。

英文:

You said there are many random errors in the console so why not try creating a Singleton class to manage Database? You can create something like this:

class DataManager {

    static let shared = DataManager()

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: &quot;Category&quot;) // Your Database Name
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError(&quot;Unresolved error \(error), \(error.userInfo)&quot;)
            }
        })
        return container
    }()

    // fetch data from the database
    func fetchData() -&gt; [Category] {
        let fetchRequest: NSFetchRequest&lt;Category&gt; = Category.fetchRequest()
        do {
            let result = try persistentContainer.viewContext.fetch(fetchRequest)
            return result
        } catch {
            print(&quot;Failed to fetch data: \(error)&quot;)
            return []
        }
    }

    // save data
    func saveData() {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print(&quot;Failed to save data: \(error)&quot;)
            }
        }
    }

    // Add data item into the database
    func addData(categoryName: String, categoryIcon: Data?, categorySearchKey: String, person: NSSet?, country: NSSet) {
        let context = persistentContainer.viewContext
        let entity = Category(context: context)
        entity.categoryName = categoryName
        if let icon = categoryIcon {
           entity.categoryIcon = icon
        }
        entity.categorySearchKey = categorySearchKey
        if let categoryPerson = person {
           entity.person = categoryPerson
        }
        entity.country = country

        saveData()
    }

    // Delete Data
    func deleteAllData() {
        let fetchRequest: NSFetchRequest&lt;NSFetchRequestResult&gt; = NSFetchRequest(entityName: &quot;Category&quot;)
        let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)

        do {
            try persistentContainer.viewContext.execute(deleteRequest)
            try persistentContainer.viewContext.save()
        } catch {
            print(&quot;Failed to delete data: \(error)&quot;)
        }
    }
    
}

Then just call DataManager.shared.addData(...)

This is just a simple singleton class to manage database. You may need to modify it according to your database attributes. I just added those methods based on looking at your attributes so feel free to modify addData() method according to your requirements and then save() is called automatically. The above code will add single entity containing categoryName, person etc. And you can keep adding as much of them as you wish and later fetch like this: let categoriesArray = DataManager.shared.fetchData() and you will get array of [Category].

huangapple
  • 本文由 发表于 2023年3月1日 12:44:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75599654.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定