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

标签 swift loops core-data relationship nspredicate

问题:更新和保存速度快,数据多的表之间的关系,两个或一个表都已保存。

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

coredata Relationship

现在的问题是所有数据下载都是根据以前的数据按顺序进行的,与 SQLite 不同,我需要设置它们之间的关系,为此我必须一次又一次地搜索表并设置它们之间的关系,即时间 -消费所以我怎样才能更快地做到这一点

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

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

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

方法 1,

下载所有数据并创建它们的对象列表,然后在所有下载完成后立即存储它们,但仍然设置它们之间的关系需要时间,最糟糕的是现在循环发生了两次我必须循环创建所有类列出然后再次循环以将这些存储在 TableView 中,但仍然没有解决关系耗时问题。

方法 2

像下载 channel 一样一个一个下载存储它们然后下载时间表存储它们然后下载节目然后将它们存储在核心数据中这一切都可以但是现在 channel 与时间表有关系并且时间表与节目有关系并设置存储时间表时的关系我还获取与该时间表相关的 channel ,然后设置关系,对于节目和时间表来说也是如此,下面是代码,所以我该如何解决这个问题或者我应该如何下载和存储它成为尽可能快。

只存储日程的代码

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

还有如何使用自动释放池进行正确的批量保存

PS:我找到的所有核心数据相关的资料都很贵,3k多,而且免费的,信息不多,都是基本的东西,连apple docs都没有太多性能调优和批量更新相关的代码和交接关系。在此先感谢您的帮助。

最佳答案

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

队列和批处理

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

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

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

每次插入到核心数据中都会创建它自己的上下文,它自己获取、保存它然后丢弃对象。不要从其他任何地方访问这些对象会导致崩溃——核心数据不是线程安全的。也只能使用此队列写入核心数据,否则会发生写入冲突。 (有关此设置的更多信息,请参阅 NSPersistentContainer concurrency for saving to core data)。

查找 map

现在您要尝试插入 300 个左右的实体,每个实体都必须查找或创建相关实体。您可能有一些散布在周围的功能可以完成此操作。如果您在不考虑性能的情况下编写此程序,您将很容易执行 300 甚至 600 个获取请求。相反,您执行单个提取 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  } }
  }

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

优先过滤关系而不是获取

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

要快得多

分析和调整

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

关于swift - 在一个或两个表已经保存后,如何加快表之间的更新关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50055953/

相关文章:

swift - 循环遍历 Firebase 数据库以在 Swift 中构建表列表

ios - 在类 Initializer 中分配成员时在 Xcode 中向自身分配属性警告

ios - Swift 变量在内存中丢失但在调试中可见

java - 在一周的时间跨度内每天按时间戳聚合对象

loops - 普通口齿不清 : recursive call from a loop

ios - 检查 name 属性是否已存在于 CoreData 中

ios - 在 iOS10+ 播放完成后,如何从锁屏中消失播放器控件?

c++ - 未延迟的无限 while 循环是不好的做法吗?

swift - TableView 选择存储到核心数据实体

ios - 从数据库加载实体而不是直接从数据库提取数据