问题:更新并快速保存表格之间的关系,包含大量数据或其中一个表格已经保存。
我有五张桌子TvGenres,TvSubgenre,TvProgram,Channels,TvSchedules以及它们之间的关系,如下图所示
现在的问题是所有数据下载都是根据以前的数据顺序发生的,而且与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)
}
如何使用autoreleas pool进行正确的批量保存
PS:我发现的所有与核心数据相关的资料都是昂贵的,花费超过3k,并且免费,没有太多的信息只是基本的东西,即使苹果文档没有太多与性能相关的代码调整和批量更新和处理关系。提前感谢任何帮助。答案 0 :(得分:3)
我以前有过这样的项目。没有一个解决方案可以解决所有问题,但是这些对您有很大帮助:
似乎您试图一次插入所有内容,然后尝试一个接一个地插入。在我的应用程序中,我发现大约300个是最佳批处理大小,但是您必须对其进行调整才能查看其在您的应用程序中的有效状态,它可能多达5000个或最少100个。从300开始并进行调整以查看有什么改进结果。
您正在进行一些处理,提到了下载并保存到数据库,但是如果您还没有提及更多内容,我也不会感到惊讶。队列(NSOperationsQueue)是一个很棒的工具。您可能认为排队会降低速度,但是事实并非如此。当您尝试一次执行太多操作时,事情就会变慢。
因此,您有一个正在下载信息的队列(我建议限制为4个并发请求),以及一个正在将数据保存到核心数据的队列(将并发限制为1以免发生写冲突)。每个下载任务完成时,会将结果缩小为更易于管理的大小,并将队列写入数据库。不用担心最后一批是否比其余的要小。
每个插入到核心数据中的数据都会创建自己的上下文,自己进行提取,保存并随后丢弃对象。不要从您的其他任何地方访问这些对象,否则将导致崩溃-核心数据不是线程安全的。也只能使用此队列写入核心数据,否则会发生写入冲突。 (有关此设置的更多信息,请参见NSPersistentContainer concurrency for saving to core data。
现在,您尝试插入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 } }
}
一旦有了此查找映射,就不要丢失它。将其传递给需要它的每个函数。如果创建对象,请考虑将其随后插入字典中。在核心数据操作中,此查找字典是您最好的朋友。不过要小心。该对象包含不是线程安全的ManagedObjects。您可以在数据库块的开头创建该对象,并且必须在结尾将其丢弃。
您没有任何明确处理此问题的代码,但是如果您遇到它,我也不会感到惊讶。假设您有一个特定的TvSchedule
,并且您想查找特定语言的日程表中的所有程序。这样做的自然方法是创建一个看起来像这样的谓词:“ TvSchedule ==%@ AND langId ==%@”。但是实际上mySchedule.programs.filter {%@.langId = myLangId }
我看到您已经在代码中添加了日志,以查看所需的时间,这确实很好。我还建议使用xCode的配置文件工具。这对于查找大多数情况下需要使用的功能确实非常有用。