Search code examples
objective-cparsingnsxmlparser

How to improve performance with NSXMLParser


i'm working with big xml file and need to download and parse him . inside 65k objects, but parsing is more then minute. I cannot understand how to optimize loading/parsing, please help me with advice. Also, because of long work cycle, need to big amount of memory and i don't know how to reduce memory consumption.

 AFXMLRequestOperation *operation = [AFXMLRequestOperation 
    XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
                XMLParser.delegate = delegate;
                [XMLParser parse];

                if (delegate.done) {
                    NSLog(@"done");
                }
            } failure:nil];
            [operation start];

- (void)parserDidStartDocument:(NSXMLParser *)parser {
    _done = NO;
    _items = [NSMutableArray new];

    _isItem = NO;
    _isPrice = NO;
    _isDetail = NO;
}

// parsing Ended
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    _done = YES;
}

// if parsing error
-(void) parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
        _done = YES;
        _error = parseError;
}

// if validation error
-(void) parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError {
    _done = YES;
    _error = validationError;
}
// new element to parse
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {

        if ([elementName isEqualToString:kItem]) {
            // если да - создаем строку в которую запишем его значение
            VZMenuItem *item = [[VZMenuItem alloc] init];
            _item = item;
            _item.name = attributeDict[@"name"];
            _item.code = attributeDict[@"code"];
            _isItem = YES;
            return;
        } else if ([elementName isEqualToString:kAttributes]) {
            VZRecipe *recipe = [[VZRecipe alloc] init];
            recipe.weight = [attributeDict[@"weight"] floatValue];
            recipe.sugar = [attributeDict[@"sugar"] floatValue];
            recipe.calories = [attributeDict[@"calories"] intValue];
            recipe.milk = [attributeDict[@"milk"] floatValue];
            recipe.eggs = [attributeDict[@"eggs"] unsignedIntValue];
            _item.recipe = recipe;
            return;
        } else if ([elementName isEqualToString:kPrice]) {
            _isPrice = YES;
            return;
        } else if ([elementName isEqualToString:kDetail]) {
            _isDetail = YES;
        }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

        if ([elementName isEqualToString:kItem]) {
            [_items addObject:_item];
        }
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

        if(_isPrice) {
            NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
            _item.price = [formatter numberFromString:string];
            _isPrice = NO;
        } else if(_isDetail) {
            _item.detailUrl = string;
            _isDetail = NO;
        }
}

CPU/Memory


Solution

  • You should try parsing the XML file from a stream. You can initialise your NSXMLParser initWithStream: which takes a NSInputStream as argument, it will read and parse data in batches from the stream object.

    You can create your NSInputStream with initWithURL: passing the URL from which to download the xml file. When you initialise the NSXMLParser with the stream it will automatically open and read the stream.

    This will give you smaller responses more often over time using less memory and hopefully less CPU.

    There's a slight hint to follow with this approach: Apple says that NSXMLParser open the stream and starts reading, but by doing this even if you call: [parser setDelegate:self] the delegate methods are not called if you don't call [parser parse]. The trick here is to call parse in a GCD block:

     xmlParser = [[NSXMLParser alloc] initWithStream:inputStream];   [xmlParser setDelegate:self];
     dispatch_block_t dispatch_block = ^(void) {      
         [xmlParser parse];   
     };
    dispatch_queue_t dispatch_queue = dispatch_queue_create("parser.queue", NULL);
    dispatch_async(dispatch_queue, dispatch_block);   
    dispatch_release(dispatch_queue);
    

    Hope it helps.