我们想要将一个巨大的XML文件(13MB)导入核心数据。目前,XML文件包含大约64000个条目,但这个数字将来还会增加。
XML结构:
<entry name='...' doctype='' last-modified='...' [some more attributes] />
经过大量的研究,包括XMLSchema示例项目、Ray Wenderlich XML教程和一些stackoverflow条目,我们还没有找到解决方案。
我们首先下载XML文件,然后开始解析并将数据插入CoreData
以下是我们的实现:
- (void)importXMLFile:(NSString*)fileName {
NSInputStream* theStream = [[NSInputStream alloc] initWithFileAtPath:fileName];
_theParser = [[NSXMLParser alloc] initWithStream:theStream];
_theParser.delegate = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[_theParser parse];
});
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:@"entry"]) {
Importer* __weak weakSelf = self;
NSManagedObjectContext* theContext = self.importContext;
[theContext performBlock:^{
CustomObject* mo;
// Create ManagedObject
// Read values from parsed XML element
dispatch_async(dispatch_get_main_queue(), ^{
// Call a handler, just for information "added object"
});
NSError *error = nil;
if ([theContext hasChanges] && ![theContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
} else {
DLOGError(error);
}
}];
}
}
使用这种方法,内存使用量会爆炸式增长,从而导致崩溃。XML文件似乎在Core Data
处理一个块之前就被完全解析了。所以问题是:
是否可以处理XML文件的一部分(例如一次30个条目),而不是保存到CoreData
,然后继续解析?
或者更常见的问题是:如何优化内存使用?
您希望使用基于流的解析器,这样就不需要同时将整个XML加载到内存中。也许是github的这个或什么。
您还应该批处理保存操作。不要保存每个单独的对象,保存一组可能有100个对象的对象。如果这是在一个紧密的循环中,你应该有一个自动释放池。
猜测我们的内存问题发生在创建ManagedObject
时未发布的一行中。我们不得不释放xmlChar
代替
xmlChar *xmlString = xmlTextReaderGetAttribute(reader, (xmlChar*)"someAttribute");
NSString *someAttributeToString = [NSString stringWithUTF8String:(const char *)xmlString];
我们使用
xmlChar * nameString = xmlTextReaderGetAttribute(reader, (xmlChar*)"someAttribute");
if (attributeString)
{
[elementDict setValue:[NSString stringWithUTF8String:(const char*)attributeString] forKey:@"someAttribute"];
xmlFree(nameString);
}
在解析了100个元素之后,我们暂停解析器并等待,直到这些元素被写入CoreData
。之后,我们解析下一个100束
分析程序
// Start the data parse
- (void) parse {
_dictionaryQeue = [NSMutableArray new];
xmlTextReaderPtr reader = xmlReaderForMemory([data bytes], [data length], NULL, NULL,
(XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
if (!reader) {
NSLog(@"Failed to create xmlTextReader");
return;
}
while (xmlTextReaderRead(reader)) {
@autoreleasepool {
while (_isPaused) {
//[NSThread sleepForTimeInterval:0.1];
}
switch (xmlTextReaderNodeType(reader)) {
case XML_READER_TYPE_ELEMENT: {
NSMutableDictionary* elementDict = [NSMutableDictionary new];
//Create Object
xmlChar * nameString = xmlTextReaderGetAttribute(reader, (xmlChar*)"name");
if (nameString)
{
[elementDict setValue:[NSString stringWithUTF8String:(const char*)nameString] forKey:@"name"];
xmlFree(nameString);
}
//...
if (self.collectDictionaries) {
[_dictionaryQeue addObject:elementDict];
NSArray* dictArray = [NSArray arrayWithArray:_dictionaryQeue];
if ([dictArray count] == self.maxCollectedDictionaries) {
dispatch_async(dispatch_get_main_queue(), ^{
if (saxDelegate && [(NSObject*)saxDelegate respondsToSelector:@selector(SAXDictionaryElements:finished:)]) {
[saxDelegate SAXDictionaryElements:dictArray finished:FALSE];
}
});
[_dictionaryQeue removeAllObjects];
_isPaused = TRUE;
}
}
elementDict = nil;
}
break;
case XML_READER_TYPE_END_ELEMENT: {
DLOGcomment(@"XML_READER_TYPE_END_ELEMENT");
if (self.collectDictionaries) {
NSArray* dictArray = [NSArray arrayWithArray:_dictionaryQeue];
if ([dictArray count] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
if (saxDelegate && [(NSObject*)saxDelegate respondsToSelector:@selector(SAXDictionaryElements:finished:)]) {
[saxDelegate SAXDictionaryElements:dictArray finished:TRUE];
}
});
data = nil;
[_dictionaryQeue removeAllObjects];
_dictionaryQeue = nil;
}
}
}
break;
}
}
}
xmlTextReaderClose(reader);
xmlFreeTextReader(reader);
reader = NULL;
}
基于DOM的解析器非常方便(TBXML、TouchXML、KissXML、TinyXML、GDataXML、RaptureXML等),尤其是那些支持XPATH的解析器。但是,当创建DOM时,内存就成了一个问题。
我正在分阶段使用相同的内存约束,所以我开始查看Libxml2XmlTextReader的包装器,到目前为止,我只找到了一个IGXMLReaderTR
IGXMLReader解析XML文档的方式类似于游标移动阅读器得到一个XML文档,并返回一个节点(IGXMLReader对象)到nextObject的每个调用。
示例,
IGXMLReader* reader = [[IGXMLReader alloc] initWithXMLString:@"<x xmlns:edi='http://ecommerce.example.org/schema'>
<edi:foo>hello</edi:foo>
</x>"];
for (IGXMLReader* node in reader) {
NSLog(@"node name: %@", node.name);
}
这与NSXMLParser的方法不同。