简体   繁体   English

对核心数据执行提取时出现间歇性崩溃

[英]Intermittent crash when performing fetch on core data

I have a published app that is occasionally crashing when performing a core data fetch. 我有一个已发布的应用程序,执行核心数据提取时有时会崩溃。 I know where the crashes are occurring, however I can't figure out why they are happening. 我知道崩溃发生的位置,但是我不知道为什么发生崩溃。

The app uses a tab view controller, and the crash occurs in a tab that has a tableView as the initial view. 该应用程序使用选项卡视图控制器,并且崩溃发生在以tableView作为初始视图的选项卡中。 Because I need this tableView to update whenever data is changed elsewhere in the app, I'm performing a fetch in the viewWillAppear method. 因为每当在应用程序的其他地方更改数据时,我都需要更新该tableView,因此我在viewWillAppear方法中执行访存。

Here is the relevant code: 以下是相关代码:

lazy var fetchedResultsController: NSFetchedResultsController<Day> = {
    let request: NSFetchRequest<Day> = Day.fetchRequest()
    request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
    request.predicate = NSPredicate(format: "calories > %@", "0")

    let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

    return frc
}()

override func viewWillAppear(_ animated: Bool) {
    do {
        try fetchedResultsController.performFetch()
    } catch {
        print("Could not fetch results")
    }
    tableView.reloadData()
}

And here is an image of the call stack. 这是调用堆栈的图像。

调用堆栈

I haven't been able to recreate the crash on my device or on the simulator, so I really don't know how to go about fixing this bug. 我无法在设备或模拟器上重新创建崩溃,因此我真的不知道如何解决此错误。 I'd appreciate any advice on solving this. 对于解决此问题的任何建议,我将不胜感激。

Here's a screenshot of the calories attribute in the core data model. 这是核心数据模型中calories属性的屏幕截图。

在此处输入图片说明

Here's the class method for creating a Day entity. 这是用于创建Day实体的类方法。

    class func dayWithInfo(date: Date, inManagedContext context: NSManagedObjectContext) -> Day {
    let defaults = UserDefaults.standard

    let formatter = DateFormatter()
    formatter.dateStyle = .full
    let dateString = formatter.string(from: date)

    let request: NSFetchRequest<Day> = Day.fetchRequest()
    request.predicate = NSPredicate(format: "dateString = %@", dateString)

    if let day = (try? context.fetch(request))?.first {
        if let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) {
            // Update the macro goals if the date is the current or future date
            if date > yesterday {
                day.proteinGoal = defaults.double(forKey: Constants.UserDefaultKeys.proteinValueKey)
                day.carbohydrateGoal = defaults.double(forKey: Constants.UserDefaultKeys.carbohydratesValueKey)
                day.lipidGoal = defaults.double(forKey: Constants.UserDefaultKeys.lipidsValueKey)
                day.fiberGoal = defaults.double(forKey: Constants.UserDefaultKeys.fiberValueKey)
                day.calorieGoal = defaults.double(forKey: Constants.UserDefaultKeys.caloriesValueKey)
            }
        }

        return day
    } else {
        let day = Day(context: context)

        // Set the date as a string representation of a day
        day.dateString = dateString
        day.date = date

        // Set the calorie and macronutrient goals from user defaults
        day.proteinGoal = defaults.double(forKey: Constants.UserDefaultKeys.proteinValueKey)
        day.carbohydrateGoal = defaults.double(forKey: Constants.UserDefaultKeys.carbohydratesValueKey)
        day.lipidGoal = defaults.double(forKey: Constants.UserDefaultKeys.lipidsValueKey)
        day.fiberGoal = defaults.double(forKey: Constants.UserDefaultKeys.fiberValueKey)
        day.calorieGoal = defaults.double(forKey: Constants.UserDefaultKeys.caloriesValueKey)

        // Creat meals and add their ralationship to the day
        if let meals = defaults.array(forKey: Constants.UserDefaultKeys.meals) as? [String] {
            for (index, name) in meals.enumerated() {
                _ = Meal.mealWithInfo(name: name, day: day, slot: index, inManagedContext: context)
            }
        }
        return day
    }

}

Allow me to posit a theory about what I think is happening. 请允许我提出关于我认为正在发生的事情的理论 I might be making incorrect assumptions about your project, so please correct anything I get wrong. 我可能对您的项目做出了错误的假设,因此请更正我弄错的任何事情。 You likely understand all the below, but I'm trying to be thorough, so please excuse anything obvious. 您可能会理解以下所有内容,但我正在尝试更详尽的内容,因此,请原谅任何明显的事情。

You have a calories attribute of your model class that is implemented as an optional attribute with no default at the model level . 您具有模型类的calories属性,该属性作为可选属性实现,在模型级别没有默认属性。 Something like the below. 如下所示。

可选属性

You also do not implement func validateCalories(calories: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool in your managed object subclass, so it's possible to save an instance with a nil (remember that Core Data is still Objective-C, so your Double Swift attribute is an NSNumber in Core Data, so nil is perfectly valid for an optional attribute). 您也没有实现func validateCalories(calories: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool在托管对象子类中使用func validateCalories(calories: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool ,因此可以将实例保存为nil(请记住,Core Data仍然是Objective-C,所以你的双斯威夫特属性是一个NSNumber的核心数据,所以零是完全合法的可选属性)。

You can specify that an attribute is optional — that is, it is not required to have a value. 您可以指定一个属性为可选-即,它不需要具有值。 In general, however, you are discouraged from doing so — especially for numeric values (typically you can get better results using a mandatory attribute with a default value — in the model — of 0). 但是,通常不鼓励您这样做-特别是对于数字值(通常,在模型中,使用默认值为0的强制属性可以得到更好的结果)。 The reason for this is that SQL has special comparison behavior for NULL that is unlike Objective-C's nil. 原因是SQL对NULL具有特殊的比较行为,这与Objective-C的nil不同。 NULL in a database is not the same as 0, and searches for 0 will not match columns with NULL. 数据库中的NULL不等于0,并且搜索0将不匹配具有NULL的列。

At this point, it would be easy to confirm the hypothesis. 在这一点上,很容易确认该假设。 Simply edit the SQLite database used for Core Data by your app in the simulator and set the calories attribute of a single record to null and see if you crash in the same way. 只需在模拟器中编辑应用程序用于核心数据的SQLite数据库,并将单个记录的calories属性设置为null ,即可查看是否以相同的方式崩溃。

If so, do a Core Data migration on your next release and remove the Optional designation on the model attribute and provide a Default value of zero. 如果是这样,请在您的下一个版本中进行核心数据迁移,并删除模型属性上的“ Optional名称,并提供Default值为零。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM