目标C:如何将巨大的XML数据导入核心数据



我们想要将一个巨大的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的方法不同。

最新更新