Objective-c NSXMLParser with Coredata 解析和存储数据的时间太长
Objective-c NSXMLParser with Coredata takes too long to parse and store data
我不是经验丰富的 xcode/objective-c 程序员,所以如果您可能不理解我可能说的某些话,或者我可能说错的话,我很抱歉。
所以一切都始于苹果拒绝我们的应用程序,因为它说:
"您的应用程序在二进制文件中包含的内容不足以使应用程序在启动时正常运行,我们需要先下载或解压缩其他资源才能使用它。 “
因为我们的应用程序是一个传输应用程序,它需要从服务中下载动态数据,以保持应用程序具有最准确和最新的数据。所以基本上每次我们打开应用程序时,我们都会要求用户下载数据(~2.5 MB),但由于苹果拒绝批准该应用程序,我破例让用户进入而不下载任何数据,但它需要转换本地 XML 文件到核心数据数据库。
我的问题是,这个大约 2.5 MB space 约 17k 行的文件 至少需要 2 分钟 来读取和存储所提供的数据。
所以我尝试查看解析器是否是问题所在,但代码对我来说似乎没问题。
我知道我正在做的事情可能不是解决方案,因为 Apple 所说的“解压额外资源”所以我认为它不会通过应用程序验证,但我仍然希望它能用更少的时间进行解析和存储...
这是我的代码:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
// <Network LinesNumber="28" ZonesNumber="112" StopsNumber="114">
if ([elementName isEqualToString:@"Network"]) {
int ln = [[attributeDict objectForKey:@"LinesNumber"] intValue];
int zn = [[attributeDict objectForKey:@"ZonesNumber"] intValue];
int sn = [[attributeDict objectForKey:@"StopsNumber"] intValue];
totalItems = (totalItems + ln + sn + zn) *1.0;
updateaction(0);
}
else if ([elementName isEqualToString:@"Stop"]) {
int idStop = [[attributeDict objectForKey:@"Id"] intValue];
NSString* name = [[attributeDict objectForKey:@"Name"] capitalizedString];
NSString* codName = [attributeDict objectForKey:@"CodName"];
int idZona = [[attributeDict objectForKey:@"IdZona"] intValue];
int idDistrito = [[attributeDict objectForKey:@"IdCounty"] intValue];
int idConcelho = [[attributeDict objectForKey:@"IdDistrict"] intValue];
int idFreguesia = [[attributeDict objectForKey:@"IdParish"] intValue];
double latitude = [[attributeDict objectForKey:@"CoordYY"] doubleValue];
double longitude = [[attributeDict objectForKey:@"CoordXX"] doubleValue];
NSString* obs = [attributeDict objectForKey:@"Observation"];
OperatorZone *zone = [Database operatorZoneFromId:idZona];
Stop *stop = [Database createStop:idStop withName:name withCodName:codName withIdZona:idZona withIdDistrito:idDistrito withIdConcelho:idConcelho withIdFreguesia:idFreguesia withLatitude:latitude withLongitude:longitude withObservations:obs withOperatorZone:zone];
[stop setCicID:cicID];
[stop setOperatorID:operatorID];
NSLog(@"Saving stop with Name: %@, cicID: %@, operatorID: %@", name, cicID, operatorID);
[stops_dict setObject:stop forKey: [NSNumber numberWithInt:idStop]];
if (zone != nil) {
[zone addStopsObject:stop];
// [[zone managedObjectContext] MR_saveToPersistentStoreAndWait]; // TIRAR ISTO DAQUI E POR NO FIM DE TUDO
}
itemcount++;
progress = itemcount/totalItems;
updateaction(progress);
}
else if ([elementName isEqualToString:@"Line"]) {
// NSLog(@"Checking line..");
int sid = [[attributeDict objectForKey:@"Id"] intValue];
NSString * name = [attributeDict objectForKey:@"LineName"];
NSString * returnName = [attributeDict objectForKey:@"ReturnLineName"];
NSString * companyID = [attributeDict objectForKey:@"CompanyId"];
int isCircular = [[attributeDict objectForKey:@"IsCircular"] boolValue];
int idOperator = [[attributeDict objectForKey:@"IdOperator"] intValue];
NSString * version = [attributeDict objectForKey:@"Version"];
currentLine = [Database createLine:sid withName:name withReturnName:returnName isCircular:isCircular withOperatorID:idOperator withCompanyID:companyID withVersion:version];
latestLineOpID = idOperator;
[currentLine setCicID:cicID];
[currentLine setOperatorID:operatorID];
lineWithOwnStops = (idOperator == suboperatorid);
itemcount++;
progress = itemcount/totalItems;
updateaction(progress);
}
XML文件数据是这样的:
<Network CountiesNumber="0" ContactsNumber="2" LinesNumber="326" StopsNumber="3161" ZonesNumber="2866">
<Zones>
<OperatorZone Name="Cavadas (R 25 Abril, 60) Café O Renascer" Id="20274" />
</Zones>
<Stops>
<Stop Id="108591" Name="Setúbal (Avª 22 Dezembro, 25)" CodName="2" IdZona="22793" CoordXX="-8.89310700" CoordYY="38.52755000" Observation="" />
</Stops>
<Lines>
<Line ReturnLineName="Cacilhas - Cristo Rei" LineName="Cristo Rei - Cacilhas" IsCircular="false" CompanyId="101" IdOperator="84" Id="16344" Version="05-08-2019 00:00:00">
<StopLines>
<StopLine StopName="0" OrderPath_I="1" OrderPath_V="0" ZoneId="20435" Id="56356194" IdStop="109346" />
<StopLine StopName="0" OrderPath_I="2" OrderPath_V="0" ZoneId="20423" Id="56356195" IdStop="109838" />
</StopLines>
</Line>
</Lines>
</Network>
编辑 - 示例数据库方法:
+ (Stop *)createStop:(int)id withName:(NSString*)name withCodName:(NSString *)codName
withIdZona:(int)idZona
withIdDistrito:(int)idDistrito
withIdConcelho:(int)idConcelho
withIdFreguesia:(int)idFreguesia
withLatitude:(double)latitude
withLongitude:(double)longitude
withObservations:(NSString *)observations
withOperatorZone:(OperatorZone *)operator
{
Stop * stop = [Stop MR_createEntity];
stop.ownStop = false;
stop.name = name;
stop.codName = codName;
stop.idZona = [NSNumber numberWithInt:idZona];
stop.idDistrito = [NSNumber numberWithInt:idDistrito];
stop.idConcelho = [NSNumber numberWithInt:idConcelho];
stop.idFreguesia = [NSNumber numberWithInt:idFreguesia];
stop.id = [NSNumber numberWithInt:id];
stop.latitude = [NSNumber numberWithDouble:latitude];
stop.longitude = [NSNumber numberWithDouble:longitude];
if (idDistrito != 0){
NSLog(@"bla bla bla");
}
stop.distrito = [Distrito MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idDistrito]];
stop.concelho = [Concelho MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idConcelho]];
stop.freguesia = [Freguesia MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idFreguesia]];
stop.operatorzone = operator;
//ac [[stop managedObjectContext] MR_saveToPersistentStoreAndWait];
// NSLog(@"Stop %d - %@ saved", [stop.id intValue], stop.name);
return stop;
}
还有一个解析器示例,这很可能是我的:
https://gist.github.com/xslim/1020767
区别在于,他使用 NSEntityDescription insertNewObjectForEntityForName:
而我使用 MR_createEntity
一般来说,如果您希望您的应用程序尽可能快地启动 - 不要在启动时执行它需要的工作。要么在之前(在构建时)做,要么推迟,尽可能在后台做。
在这个特定的示例中,我建议 运行 在应用程序构建期间将代码转换为 XML 到 sqlite/CoreData,并将预填充的数据库作为资源发送到应用程序包。
注意:可以制作一个 macOS 工具来执行此操作,然后将该工具用于您的应用程序目标作为构建阶段。
如果您想加快服务器的更新速度,请尝试在后台进行处理。使用分析工具查看最慢的部分。在批量导入 SQL 期间,最慢的部分之一通常与更新索引有关。有时可以禁用它们,插入所有内容,然后从头开始构建索引,而不是为每个新行更新索引。这可能会快得多,但这需要更深入地了解 CoreData 如何使用索引。有时最慢的部分与搜索有关。除了使用数据库搜索之外,还可以在内存中构建字典并使用它进行更快的搜索。
我终于找到了解决办法。所以我决定使用预加载的数据库,而不是从本地 xml 文件填充我的数据库数据。
基本上我所做的是:
- 在 运行 具有本地 xml 文件的应用程序之后,我让它创建 BD,然后转到 ~Library/Developer/... 以获取生成的 DB 文件,我有 3 个文件,但如果我使用名为 DB Browser for SQLite 的程序打开它们,则 3 个文件合并为 1 个文件。
- 之后,我抓取了唯一的 1 个 DB 文件,然后将其移动到我的项目所需的 Target 中。假设我称它为 MyDB.sqlite
然后在我的 AppDelegate 文件中 didFinishLaunchingWithOptions:
添加了这段代码:
// This code is needed to preload the database
NSArray *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *documentPath = [paths lastObject];
NSURL *storeURL = [documentPath URLByAppendingPathComponent:@"MyDB.sqlite"];
// Check if the database already exists in the document folder, if it doesn't exist add it to the documents folder
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MyDB" ofType:@"sqlite"]];
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(@"Error: Unable to copy preloaded database.");
}
}
// Setup Magical Record in the document path
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:storeURL];
出于某种原因 setupCoreDataStackWithAutoMigratingSqliteStoreNamed
对我不起作用(也许是因为从其他目标获取 MyDB.sqlite 文件?我不知道),所以我决定继续setupCoreDataStackWithAutoMigratingSqliteStoreAtURL
我不是经验丰富的 xcode/objective-c 程序员,所以如果您可能不理解我可能说的某些话,或者我可能说错的话,我很抱歉。
所以一切都始于苹果拒绝我们的应用程序,因为它说:
"您的应用程序在二进制文件中包含的内容不足以使应用程序在启动时正常运行,我们需要先下载或解压缩其他资源才能使用它。 “
因为我们的应用程序是一个传输应用程序,它需要从服务中下载动态数据,以保持应用程序具有最准确和最新的数据。所以基本上每次我们打开应用程序时,我们都会要求用户下载数据(~2.5 MB),但由于苹果拒绝批准该应用程序,我破例让用户进入而不下载任何数据,但它需要转换本地 XML 文件到核心数据数据库。
我的问题是,这个大约 2.5 MB space 约 17k 行的文件 至少需要 2 分钟 来读取和存储所提供的数据。
所以我尝试查看解析器是否是问题所在,但代码对我来说似乎没问题。
我知道我正在做的事情可能不是解决方案,因为 Apple 所说的“解压额外资源”所以我认为它不会通过应用程序验证,但我仍然希望它能用更少的时间进行解析和存储...
这是我的代码:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
// <Network LinesNumber="28" ZonesNumber="112" StopsNumber="114">
if ([elementName isEqualToString:@"Network"]) {
int ln = [[attributeDict objectForKey:@"LinesNumber"] intValue];
int zn = [[attributeDict objectForKey:@"ZonesNumber"] intValue];
int sn = [[attributeDict objectForKey:@"StopsNumber"] intValue];
totalItems = (totalItems + ln + sn + zn) *1.0;
updateaction(0);
}
else if ([elementName isEqualToString:@"Stop"]) {
int idStop = [[attributeDict objectForKey:@"Id"] intValue];
NSString* name = [[attributeDict objectForKey:@"Name"] capitalizedString];
NSString* codName = [attributeDict objectForKey:@"CodName"];
int idZona = [[attributeDict objectForKey:@"IdZona"] intValue];
int idDistrito = [[attributeDict objectForKey:@"IdCounty"] intValue];
int idConcelho = [[attributeDict objectForKey:@"IdDistrict"] intValue];
int idFreguesia = [[attributeDict objectForKey:@"IdParish"] intValue];
double latitude = [[attributeDict objectForKey:@"CoordYY"] doubleValue];
double longitude = [[attributeDict objectForKey:@"CoordXX"] doubleValue];
NSString* obs = [attributeDict objectForKey:@"Observation"];
OperatorZone *zone = [Database operatorZoneFromId:idZona];
Stop *stop = [Database createStop:idStop withName:name withCodName:codName withIdZona:idZona withIdDistrito:idDistrito withIdConcelho:idConcelho withIdFreguesia:idFreguesia withLatitude:latitude withLongitude:longitude withObservations:obs withOperatorZone:zone];
[stop setCicID:cicID];
[stop setOperatorID:operatorID];
NSLog(@"Saving stop with Name: %@, cicID: %@, operatorID: %@", name, cicID, operatorID);
[stops_dict setObject:stop forKey: [NSNumber numberWithInt:idStop]];
if (zone != nil) {
[zone addStopsObject:stop];
// [[zone managedObjectContext] MR_saveToPersistentStoreAndWait]; // TIRAR ISTO DAQUI E POR NO FIM DE TUDO
}
itemcount++;
progress = itemcount/totalItems;
updateaction(progress);
}
else if ([elementName isEqualToString:@"Line"]) {
// NSLog(@"Checking line..");
int sid = [[attributeDict objectForKey:@"Id"] intValue];
NSString * name = [attributeDict objectForKey:@"LineName"];
NSString * returnName = [attributeDict objectForKey:@"ReturnLineName"];
NSString * companyID = [attributeDict objectForKey:@"CompanyId"];
int isCircular = [[attributeDict objectForKey:@"IsCircular"] boolValue];
int idOperator = [[attributeDict objectForKey:@"IdOperator"] intValue];
NSString * version = [attributeDict objectForKey:@"Version"];
currentLine = [Database createLine:sid withName:name withReturnName:returnName isCircular:isCircular withOperatorID:idOperator withCompanyID:companyID withVersion:version];
latestLineOpID = idOperator;
[currentLine setCicID:cicID];
[currentLine setOperatorID:operatorID];
lineWithOwnStops = (idOperator == suboperatorid);
itemcount++;
progress = itemcount/totalItems;
updateaction(progress);
}
XML文件数据是这样的:
<Network CountiesNumber="0" ContactsNumber="2" LinesNumber="326" StopsNumber="3161" ZonesNumber="2866">
<Zones>
<OperatorZone Name="Cavadas (R 25 Abril, 60) Café O Renascer" Id="20274" />
</Zones>
<Stops>
<Stop Id="108591" Name="Setúbal (Avª 22 Dezembro, 25)" CodName="2" IdZona="22793" CoordXX="-8.89310700" CoordYY="38.52755000" Observation="" />
</Stops>
<Lines>
<Line ReturnLineName="Cacilhas - Cristo Rei" LineName="Cristo Rei - Cacilhas" IsCircular="false" CompanyId="101" IdOperator="84" Id="16344" Version="05-08-2019 00:00:00">
<StopLines>
<StopLine StopName="0" OrderPath_I="1" OrderPath_V="0" ZoneId="20435" Id="56356194" IdStop="109346" />
<StopLine StopName="0" OrderPath_I="2" OrderPath_V="0" ZoneId="20423" Id="56356195" IdStop="109838" />
</StopLines>
</Line>
</Lines>
</Network>
编辑 - 示例数据库方法:
+ (Stop *)createStop:(int)id withName:(NSString*)name withCodName:(NSString *)codName
withIdZona:(int)idZona
withIdDistrito:(int)idDistrito
withIdConcelho:(int)idConcelho
withIdFreguesia:(int)idFreguesia
withLatitude:(double)latitude
withLongitude:(double)longitude
withObservations:(NSString *)observations
withOperatorZone:(OperatorZone *)operator
{
Stop * stop = [Stop MR_createEntity];
stop.ownStop = false;
stop.name = name;
stop.codName = codName;
stop.idZona = [NSNumber numberWithInt:idZona];
stop.idDistrito = [NSNumber numberWithInt:idDistrito];
stop.idConcelho = [NSNumber numberWithInt:idConcelho];
stop.idFreguesia = [NSNumber numberWithInt:idFreguesia];
stop.id = [NSNumber numberWithInt:id];
stop.latitude = [NSNumber numberWithDouble:latitude];
stop.longitude = [NSNumber numberWithDouble:longitude];
if (idDistrito != 0){
NSLog(@"bla bla bla");
}
stop.distrito = [Distrito MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idDistrito]];
stop.concelho = [Concelho MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idConcelho]];
stop.freguesia = [Freguesia MR_findFirstByAttribute:@"id" withValue:[NSNumber numberWithInt:idFreguesia]];
stop.operatorzone = operator;
//ac [[stop managedObjectContext] MR_saveToPersistentStoreAndWait];
// NSLog(@"Stop %d - %@ saved", [stop.id intValue], stop.name);
return stop;
}
还有一个解析器示例,这很可能是我的: https://gist.github.com/xslim/1020767
区别在于,他使用 NSEntityDescription insertNewObjectForEntityForName:
而我使用 MR_createEntity
一般来说,如果您希望您的应用程序尽可能快地启动 - 不要在启动时执行它需要的工作。要么在之前(在构建时)做,要么推迟,尽可能在后台做。
在这个特定的示例中,我建议 运行 在应用程序构建期间将代码转换为 XML 到 sqlite/CoreData,并将预填充的数据库作为资源发送到应用程序包。
注意:可以制作一个 macOS 工具来执行此操作,然后将该工具用于您的应用程序目标作为构建阶段。
如果您想加快服务器的更新速度,请尝试在后台进行处理。使用分析工具查看最慢的部分。在批量导入 SQL 期间,最慢的部分之一通常与更新索引有关。有时可以禁用它们,插入所有内容,然后从头开始构建索引,而不是为每个新行更新索引。这可能会快得多,但这需要更深入地了解 CoreData 如何使用索引。有时最慢的部分与搜索有关。除了使用数据库搜索之外,还可以在内存中构建字典并使用它进行更快的搜索。
我终于找到了解决办法。所以我决定使用预加载的数据库,而不是从本地 xml 文件填充我的数据库数据。
基本上我所做的是:
- 在 运行 具有本地 xml 文件的应用程序之后,我让它创建 BD,然后转到 ~Library/Developer/... 以获取生成的 DB 文件,我有 3 个文件,但如果我使用名为 DB Browser for SQLite 的程序打开它们,则 3 个文件合并为 1 个文件。
- 之后,我抓取了唯一的 1 个 DB 文件,然后将其移动到我的项目所需的 Target 中。假设我称它为 MyDB.sqlite
然后在我的 AppDelegate 文件中 didFinishLaunchingWithOptions:
添加了这段代码:
// This code is needed to preload the database
NSArray *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *documentPath = [paths lastObject];
NSURL *storeURL = [documentPath URLByAppendingPathComponent:@"MyDB.sqlite"];
// Check if the database already exists in the document folder, if it doesn't exist add it to the documents folder
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MyDB" ofType:@"sqlite"]];
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(@"Error: Unable to copy preloaded database.");
}
}
// Setup Magical Record in the document path
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:storeURL];
出于某种原因 setupCoreDataStackWithAutoMigratingSqliteStoreNamed
对我不起作用(也许是因为从其他目标获取 MyDB.sqlite 文件?我不知道),所以我决定继续setupCoreDataStackWithAutoMigratingSqliteStoreAtURL