在已经保存了一个或两个表之后,如何加快更新表之间的关系?

Ira*_*esh 6 loops core-data relationship nspredicate swift

问题:更新并快速保存表格之间的关系,其中包含大量数据或其中一个表已经保存.

我有五个表TvGenres,TvSubgenre,TvProgram,Channels,TvSchedules与它们之间的关系,如下图所示

coredata关系

现在的问题是所有的数据下载都是根据以前的数据顺序发生的,而且与SQLite不同,我需要设置它们之间的关系,为此我必须一次又一次地搜索表并设置它们之间的关系,这是很费时的,所以如何我能更快地做到这一点吗?

我使用2种不同的方法来解决,但两种方法都没有按预期工作

首先让我告诉我,下载是如何工作的

首先,我根据用户语言获取所有频道详细信息从频道,我获取下一周的所有时间表(这是大量数据(大约30k +))从调度数据,我获取所有程序数据(这也是很多数据的 )

方法1,

下载所有数据并创建它们的对象列表,然后在所有下载完成后立即存储它们但仍然设置它们之间的关系需要时间和最糟糕的事情现在循环发生两次首先我必须循环创建所有类列表然后循环再次存储在表视图中仍然没有解决关系耗时的问题.

方法2

像下载频道一样逐个下载存储它们,然后下载时间表存储它们然后下载程序然后将它们存储在核心数据中这一切都可以,但现在频道与时间表有关系,时间表与程序有关系,并设置关系,而我我存储时间表我也获取与该时间表相关的通道,然后设置关系,对于程序和时间表是相同的,下面花费时间的是代码,所以如何解决这个问题或者我应该如何下载和存储以便它变得如此快可能.

仅用于存储计划的代码

func saveScheduleDataToCoreData(withScheduleList scheduleList: [[String : Any]], completionBlock: @escaping (_ programIds: [String]?) -> Void) {
    let start = DispatchTime.now()
    let context = coreDataStack.managedObjectContext

    var progIds = [String]()
    context.performAndWait {
        var scheduleTable: TvSchedule!

        for (index,response) in scheduleList.enumerated() {
            let schedule: TvScheduleInformation = TvScheduleInformation(json: response )
            scheduleTable = TvSchedule(context: context)
            scheduleTable.channelId = schedule.channelId
            scheduleTable.programId = schedule.programId
            scheduleTable.startTime = schedule.startTime
            scheduleTable.endTime = schedule.endTime
            scheduleTable.day = schedule.day
            scheduleTable.languageId = schedule.languageId
            scheduleTable.isReminderSet = false

            //if I comment out the below code then it reduce the time significantly from 5 min to 34.74 s
            let tvChannelRequest: NSFetchRequest<Channels> = Channels.fetchRequest()
            tvChannelRequest.predicate = NSPredicate(format: "channelId == %d", schedule.channelId)
            tvChannelRequest.fetchLimit = 1
            do {
                let channelResult = try context.fetch(tvChannelRequest)
                if channelResult.count == 1 {
                    let channelTable = channelResult[0]
                    scheduleTable.channel = channelTable
                }
            }
            catch {
                print("Error: \(error)")
            }
            progIds.append(String(schedule.programId))
            //storeing after 1000 schedules 
            if index % 1000 == 0 {
                print(index)
                do {
                    try context.save()
                } catch let error as NSError {
                    print("Error saving schdeules object context! \(error)")
                }

            }
        }
    }
    let end = DispatchTime.now()
    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
    print("Saving \(scheduleList.count) Schedules takes \(nanoTime) nano time")
    coreDataStack.saveContext()
    completionBlock(progIds)
}
Run Code Online (Sandbox Code Playgroud)

另外如何使用autoreleas池进行适当的批量保存

PS:我发现与核心数据相关的所有材料都是昂贵的,花费超过3k,并且免费,没有太多信息只是基本的东西,即使苹果文档没有太多与性能调整和批量更新和处理关系相关的代码.提前感谢任何帮助.

Jon*_*ose 5

我以前也有过这样的项目。没有一个解决方案可以解决所有问题,但以下一些事情很有帮助:

队列和批处理

看起来您试图一次插入所有内容,然后尝试一项一项地执行。在我的应用程序中,我发现大约 300 是最佳批量大小,但您必须对其进行调整以查看哪些在您的应用程序中有效,它可能多达 5000 或少至 100。从 300 开始并进行调整以查看哪些会变得更好结果。

您正在进行一些进程,您提到下载并保存到数据库,但如果还有更多您没有提到的,我不会感到惊讶。队列(NSOperationsQueue)是一个很棒的工具。您可能认为排队会减慢速度,但事实并非如此。当你尝试一次做太多事情时,事情就会变得很慢。

因此,您有一个队列正在下载信息(我建议限制为 4 个并发请求),另一个队列正在将数据保存到核心数据(将并发限制为 1,以免出现写入冲突)。当每个下载任务完成时,它将结果放入更易于管理的大小和队列中以写入数据库。如果最后一批比其他批次小一点,请不要担心。

每次插入核心数据都会创建自己的上下文,进行自己的提取,保存它,然后丢弃对象。不要从其他任何地方访问这些对象,否则会崩溃 - 核心数据不是线程安全的。另外,仅使用此队列写入核心数据,否则会出现写入冲突。(有关此设置的更多信息,请参阅保存到核心数据的 NSPercientContainer 并发性)。

查找地图

现在您尝试插入 300 个左右的实体,每个实体都必须查找或创建相关实体。您可能有一些分散的函数来完成此任务。如果您在不考虑性能的情况下进行编程,您将轻松执行 300 甚至 600 个提取请求。相反,您执行一次 fetch fetchRequest.predicate = NSPredicate(format: "channelId IN %@", objectIdsIamDealingWithNow)。获取后将数组转换为以 id 作为键的字典

  var lookup:[String: TvSchedule] = [:]
  if let results = try? context.fetch(fetchRequest) {
      results.forEach { if let channelId = $0.channelId { lookup[channelId] = $0  } }
  }
Run Code Online (Sandbox Code Playgroud)

一旦您拥有此查找地图,请不要丢失它。将其传递给每个需要它的函数。如果您创建对象,请考虑随后将它们插入到字典中。在核心数据操作中,这个查找字典是你最好的朋友。不过要小心。该对象包含非线程安全的托管对象。您在数据库块的开头创建此对象,并且必须在末尾丢弃它。

优先选择过滤关系而不是获取

您没有任何明确处理此问题的代码,但如果您遇到它,我不会感到惊讶。假设您有一个特定的项目TvSchedule,并且想要查找时间表中特定语言的所有项目。执行此操作的自然方法是创建一个类似于以下内容的谓词:“TvSchedule == %@ AND langId == %@”。但实际上做起来要快得多mySchedule.programs.filter {%@.langId = myLangId }

分析和调整

我看到您已经在代码中添加日志来查看需要多长时间,这真的很好。我还建议使用 xCode 的 Profile 工具。这对于查找占用大部分时间的功能非常有用。