我有一个适用于iPad的现成企业(非AppStore)传统iOS应用程序,我需要重构(它由另一个开发人员编写,我的前任在我目前的工作中编写)。
此应用程序通过JSON从具有MSSQL数据库的服务器获取其数据。数据库模式有大约30个表格,其中最广泛的是:客户端,城市,机构各有大约10,000个记录,并且未来还有望进一步增长。收到JSON后(每个表有一个JSON请求和响应对) - 它被映射到CoreData - 这个过程还包括将相应的CoreData实体(客户端,城市,代理和其他)绑定在一起在CoreData层上设置这些实体之间的关系。
项目的CoreData fetch-part(或者read-part)本身已经过很多优化 - 我猜,它使用了CoreData几乎所有可能的性能和内存调整,这就是为什么UI层的应用速度非常快并且响应速度很快,所以我认为它的工作是完全令人满意和充分的。
问题是CoreData层的准备过程,即服务器到客户端的同步过程:它花费了太多时间。考虑导致30个JSON包的30个网络请求(“包”,我的意思是“一个表 - 一个JSON”),然后映射到30个CoreData实体,然后将它们粘合在一起(相应的CoreData关系设置在它们之间)。当我第一次看到这个项目是如何完成的时候(太慢),我想到的第一个想法是:
“第一次执行完整同步(应用程序的首次启动时间) - 在一个归档文件(类似于数据库转储)中执行整个数据库数据的提取,然后以某种方式将其作为整体导入到核心数据土地”。
但后来我意识到,即使这种单文件转储的传输是可能的,CoreData仍然会要求我执行相应CoreData实体的粘合以在它们之间设置适当的关系,以便很难想象我能够如果我依靠这个方案,就会在业绩中获益。
另外,我的同事建议我考虑将SQLite作为Core Data的完整替代品,但不幸的是,我没有使用它的经验,这就是为什么我完全盲目地预见到这种严肃的设计决定的所有后果(即使拥有同步过程非常缓慢,我的应用确实有效,尤其是它的UI性能现在非常好)。我可以想象SQLite与Core Data不同,它不会促使我在客户端粘贴一些额外的关系,因为SQLite具有良好的旧外键系统,不是吗?
所以这里是问题(受访者,当你回答时请不要混合这些要点 - 我对所有问题都有太多的困惑):
尽管Mundi写的答案很好(一个大的JSON,使用SQLite的“No”),但如果对我描述的问题有任何其他见解,我仍然感兴趣。
我确实试图用我的俄语英语来描述我的情况,希望我的问题能够让所有读者都很清楚。通过第二次更新,我将尝试提供一些更多的指导,使我的问题更加清晰。
请考虑两个二分法:
我认为非常明显的是,这两个二分法相交所形成的“扇区”,从第一个选择CoreData和第二个选择JSON是iOS开发世界中最广泛的默认设置,它也被我的应用程序使用从这个问题。
话虽如此,我声称我会很感激看到关于CoreData-JSON对的任何答案以及考虑使用任何其他“扇区”的答案(关于选择SQLite和某种类型的转储方法,为什么不呢?)
另外,值得注意的是,我不想只删除其他替代方案的当前选项,我只想让解决方案在其使用的同步和UI阶段快速工作。所以有关改进现行方案的答案以及建议其他方案的答案是值得欢迎的!
现在,请参阅以下更新#3,其中提供了有关我当前的CoreData-JSON情况的更多详细信息:
正如我所说,目前我的应用程序收到30包JSON - 整个桌子一包。以宽阔的桌子为例:客户,代理商,城市。
它是核心数据,所以如果一个client
记录有非空agency_id
字段,我需要创建一个新的Core Data的Core Data实体,Agency (NSManagedObject subclass)
并用这个记录的JSON数据填充它,这就是为什么我需要为这个类的代理机构拥有相应的Core Data实体Agency (NSManagedObject's subclass)
,最后我需要做一些事情client.agency = agency;
然后打电话[currentManagedObjectContext save:&error]
。通过这种方式完成后,我可以再请求这个客户端被提取,并要求其客户.agency
找到相应的实体。当我这样做时,我希望自己完全健康。
现在想象这种模式适用于以下情况:
我刚刚收到以下3个单独的JSON包:10000个客户和4000个城市和6000个代理(客户有一个城市,城市有很多客户;客户有代理,代理有很多客户,代理有一个城市,城市有很多代理)。
现在我想在核心数据层次上设置以下关系:我希望我的客户实体client
连接到相应的城市和相应的代理机构。
目前在项目中执行这个操作确实很丑陋:
agency
并通过city_id
它获取相应的实体city
并使用它来连接它agency.city = city
。迭代遍历整个机构的JSON数组后,保存当前的托管对象上下文(实际上, - [managedObjectContext save:]完成多次,每个处理500个记录之后)。在这一步很明显,为6000个代理机构中的每个代理机构为每个客户提取4000个城市中的一个对整个同步过程具有巨大的性能影响。这一切都很糟糕。
我在这里可以看到的唯一性能优化是,第一阶段可能会留下一个带有城市id的大字典(我的意思是NSNumber的真实id),并将城市实体作为值进行故障),因此可以防止以下丑陋的查找过程第二阶段,然后在第三阶段使用类似的缓存技巧来做同样的事情,但问题是,在所描述的[客户端 - 城市,客户机构,代理 - 城市]所有30个表格之间存在更多的关系涉及缓存所有实体的最终程序最有可能会触及iPad设备为我的应用程序保留的资源。
对未来受访者的信息:我已尽了最大努力使这个答案非常详细和完善,我真的希望你用详细的答案来回答。如果你的回答真的能够解决这里讨论的问题的复杂性,并且补充我为尽可能清晰和概括我的问题所做的努力,这将是非常好的。谢谢。
即使没有可能开放新的赏金并且有可接受的答案,我仍然很高兴看到任何其他答案包含有关本主题所解决问题的附加信息。提前致谢。
发布于 2018-03-29 08:35:07
我有一个非常类似的项目的经验。核心数据插入需要一些时间,所以我们要求用户这需要一段时间,但只是第一次。最好的性能调整当然是保存批量大小,但我相信你知道这一点。
一个性能建议:我已经尝试了几件事情,发现创建许多下载线程可能会对性能产生影响,我想是因为对于每个请求,服务器都会有一些延迟。
相反,我发现,下载所有的JSON 一气呵成快得多。我不知道你有多少数据,但是我测试了> 100.000条记录和一个40MB + JSON字符串,这非常快速,所以瓶颈只是核心数据插入。随着@autorelease
池这甚至在第一代iPad进行得体。
远离SQLite API - 它会让你花费超过一年的时间(提供高生产率)来复制Core Data提供的性能优化。
发布于 2018-03-29 10:08:31
首先,你做了很多工作,无论你如何分割它都需要一些时间,但是有一些方法可以改进。
我建议分批执行提取操作,批量大小与处理新对象的批量大小相匹配。例如,创建新Agency
记录时,请执行以下操作:
Agency
批次按排序city_id
。(我会解释为什么以后)。City
每个ID Agency
。根据你的JSON结构的不同,这可能是这样的一行代码(因为valueForKey
对数组有效):
NSArray *cityIDs = [myAgencyBatch valueForKey:@"city_id"];
City
使用您在上一步中找到的ID获取当前传递的所有实例。按结果排序city_id
。就像是:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"City"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"city_id in %@", cityIDs]; [request setPredicate:predicate]; [request setSortDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"city_id" ascending:YES] ]]; NSArray *cities = [context executeFetchRequest:request error:nil];
现在,你有一个数组Agency
和另一个数组City
,都按顺序排列city_id
。匹配他们建立关系(检查city_id
事情不匹配)。保存更改,然后继续下一批。
这将显着减少需要执行的提取次数,这会加快速度。有关此技术的更多信息,请参阅在Apple文档中实现高效查找或创建。
另一件可能有用的事情是在开始获取它们之前,用核心数据的内部缓存“热身”所需的对象。这将节省时间,因为获取属性值不需要访问数据存储。为此,你可以这样做:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"City"];
// no predicate, get everything
[request setResultType:NSManagedObjectIDResultType];
NSArray *notUsed = [context executeFetchRequest:request error:nil];
然后就忘了结果。这在表面上毫无用处,但会改变内部核心数据状态以便City
稍后更快地访问实例。
至于你的其他问题,
city_id
外键一样的使用字段。所以,快速导入。不利的一面是,你必须做你自己的工作,将你的模型对象转换成SQL记录或从SQL记录转换出来,并且可能会重写很多现有的假设核心数据的代码(例如,每次你现在关注你的关系需要通过该外键查找记录)。此更改可能会解决导入性能问题,但副作用可能很大。https://stackoverflow.com/questions/-100004321
复制相似问题