首页 > 解决方案 > 对核心数据执行提取时间歇性崩溃

问题描述

我有一个已发布的应用程序,在执行核心数据提取时偶尔会崩溃。我知道崩溃发生在哪里,但是我不知道为什么会发生。

该应用程序使用选项卡视图控制器,并且崩溃发生在以 tableView 作为初始视图的选项卡中。因为每当数据在应用程序的其他地方发生更改时,我都需要更新这个 tableView,所以我在 viewWillAppear 方法中执行获取。

以下是相关代码:

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

这是调用堆栈的图像。

调用栈

我无法在我的设备或模拟器上重新创建崩溃,所以我真的不知道如何修复这个错误。我将不胜感激有关解决此问题的任何建议。

这是calories核心数据模型中属性的屏幕截图。

在此处输入图像描述

这是创建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
    }

}

标签: iosiphoneswiftcore-datacrash-reports

解决方案


请允许我提出一个关于我认为正在发生的事情的理论。我可能对您的项目做出了错误的假设,所以请纠正我弄错的任何地方。您可能了解以下所有内容,但我正在努力做到彻底,所以请原谅任何明显的事情。

您有一个calories模型类的属性,该属性作为可选属性实现,在模型级别没有默认值。像下面这样的东西。

可选属性

您也没有func validateCalories(calories: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool在托管对象子类中实现,因此可以使用 nil 保存实例(请记住,Core Data 仍然是 Objective-C,因此您的 Double Swift 属性是 Core Data 中的 NSNumber,因此 nil 完全适用于一个可选属性)。

您可以指定一个属性是可选的——也就是说,它不需要有一个值。但是,通常不鼓励您这样做——尤其是对于数值(通常,您可以使用具有默认值(在模型中)为 0 的强制属性获得更好的结果)。原因是 SQL 对 NULL 有特殊的比较行为,这与 Objective-C 的 nil 不同。数据库中的 NULL 与 0 不同,搜索 0 将不会匹配具有 NULL 的列。

在这一点上,很容易证实这个假设。只需在模拟器中编辑应用程序用于 Core Data 的 SQLite 数据库,并将calories单个记录的属性设置为,null然后查看是否以相同的方式崩溃。

如果是这样,请在下一个版本中执行 Core Data 迁移并删除Optional模型属性上的指定并提供Default零值。


推荐阅读