【问题标题】:Intermittent crash when performing fetch on core data对核心数据执行提取时间歇性崩溃
【发布时间】:2019-01-18 11:32:21
【问题描述】:

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

应用程序使用了一个选项卡视图控制器,并且崩溃发生在一个以 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
    }

}

【问题讨论】:

  • 您的calories 列是否可以为空?当您尝试比较未实现选择器的对象时,您的比较谓词似乎失败了。
  • 卡路里属性被赋予一个默认值,所以它不应该是零,但你是对的,问题似乎与比较谓词有关。
  • calories 属性的类型是什么?
  • 不确定这与您的崩溃有多相关,但从viewWillAppear 的文档中,“如果您覆盖此方法,您必须在实现中的某个时刻调用 super。”我>
  • 谓词说明符:developer.apple.com/library/archive/documentation/Cocoa/…。本质上,您正在创建一个字符串,然后通过调用 descriptionWithLocale: 或 description: 在谓词中替换该字符串。虽然 应该 有效,但我想知道是否存在一些边缘情况。既然你知道你的属性是双精度的,你可以使用NSPredicate(format: "calories &gt; %f", 0.0),但它实际上只是增加了解析开销。我不是 Swift 人,但我认为 NSPredicate(format: "calories &gt; 0") 应该可以。

标签: ios iphone swift core-data crash-reports


【解决方案1】:

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

您的模型类有一个calories 属性,该属性是作为可选属性实现的,在模型级别。如下所示。

你也没有在你的托管对象子类中实现func validateCalories(calories: AutoreleasingUnsafeMutablePointer&lt;AnyObject?&gt;, error: NSErrorPointer) -&gt; 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 并查看您是否以相同的方式崩溃。

如果是这样,请在您的下一个版本中进行核心数据迁移,并删除模型属性上的 Optional 名称,并提供一个为零的 Default 值。

【讨论】:

  • 如果我使用可选项,这将是有意义的,但我已将属性设置为默认值。如果我不使用可选项,func validateCalories(calories: AutoreleasingUnsafeMutablePointer&lt;AnyObject?&gt;, error: NSErrorPointer) -&gt; Bool 还会有任何相关性吗?这不是我研究过的核心数据领域。
  • 鉴于您提供的新信息,这听起来没有帮助。我做了很多假设,希望能更快地找到解决方案。验证方法可能不会在您的情况下添加任何内容,因为您似乎已尽一切努力正确设置模型。话虽如此,看来谓词仍然有问题。您确定您的 datecalories 属性在您的应用的第一个版本中都是必需的并且已正确默认?
  • 当然。自该应用程序的第一个发布版本以来,核心数据没有任何变化。
猜你喜欢
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多