سؤال

I have created an NSMutableArray called tempArray which holds the characters for the three elements I want. Currently, when I set a breakpoint and let the parser run, tempArray correctly changes each time, and gives me the information I need (latitude, longitude, the-name).

Then, when the parser calls the didEndElement... method for "a-location", I add tempArray to the main NSMutableArray named parsedNearMe, which should hold all of the a-locations(each has the-name, latitude, longitude).

This all works as expected.

The Problem: Each time I add tempArray to parsedNearMe, it adds the new object, but sets all of the existing objects to the values of the new object.

What I think is the problem I think that the problem is that since I am setting the current elements to tempArray, and each time I'm overwriting every object in parsedNearMe because every object is a pointer to the same tempArray.

Possible solutions I've attempted or researched with no luck -Somehow clear the pointer so I can reuse tempArray (or reinitialize it?) -dynamically create a new name in place of tempArray each time (don't think is possible in Objective-C)

I appreciate any suggestions, as I have spent so many hours on this already I am doing circles. Also, I would kind of like the current architecture to work(getting one large array of arrays, so I can call 1 of the arrays and get all of the data I need for that annotation).

My code is as follows:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{

    if ( [elementName isEqualToString:@"a-location"]) 
    {
        if (!tempArray)
            tempArray = [[NSMutableArray alloc] init];
        return;
    }


    if ( [elementName isEqualToString:@"latitude"] ) 
    {
        latitude = [[NSMutableString alloc] init];
        return;
    } 


    if ( [elementName isEqualToString:@"longitude"] ) {
        longitude = [[NSMutableString alloc] init];
        return;
    }


    if ( [elementName isEqualToString:@"the-name"] ) {
        myName = [[NSMutableString alloc] init];
        return;
    }

}



-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    currentString = [[NSMutableString alloc]init];
    [currentString appendString:string];

}



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

    if ( [elementName isEqualToString:@"latitude"] ) {
        [tempArray addObject:currentString];
        return;
    }

    if ( [elementName isEqualToString:@"longitude"] ) {
        [tempArray addObject:currentString];
        return;
    }

    if ( [elementName isEqualToString:@"the-name"] ) {
        [tempArray addObject:currentString];
        return; 
    }

    if ( [elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe setObject:tempArray forKey:[NSDate date]];
        [tempArray removeAllObjects];
    }
}
هل كانت مفيدة؟

المحلول

There are a couple of problems:

  1. The didEndElement for a-location sets the value keyed by the date, but then takes the pointer that points to that same object and removes all the items that were in that object. So, instead of:

    if ( [elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe setObject:tempArray forKey:[NSDate date]];
        [tempArray removeAllObjects];
    }
    

    You might replace that with:

    if ( [elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe setObject:tempArray forKey:[NSDate date]];
        tempArray = nil;  // make sure you don't touch that object you just added to parsedNearMe
    }
    
  2. I doubt this particular XML will suffer from this problem, but your foundCharacters assumes that a single call will return all of the data. That's not a valid assumption. Sometimes it takes multiple calls.

  3. You're also having foundCharacters append strings for portions of the XML for which you have no interest. You should make sure that currentString is nil except when parsing latitude, longitude, and name.

So, at the very least, I'd suggest something like the following:

- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    parsedNearMe = [[NSMutableDictionary alloc] init]; 
    currentString = nil;
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if ( [elementName isEqualToString:@"a-location"]) 
    {
        tempArray = [[NSMutableArray alloc] init];
    }
    else if ([elementName isEqualToString:@"latitude"] || 
             [elementName isEqualToString:@"longitude"] || 
             [elementName isEqualToString:@"the-name"]) 
    {
        currentString = [[NSMutableString alloc] init];
    }
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    [currentString appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"latitude"] ||
        [elementName isEqualToString:@"longitude"] ||
        [elementName isEqualToString:@"the-name"] )
    {
        [tempArray addObject:currentString];
        currentString = nil;
    }
    else if ( [elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe setObject:tempArray forKey:[NSDate date]];
        tempArray = nil;
    }
}

This mirrors your current logic, creating dictionary keyed by some random timestamp, and the value for each dictionary item is an array of latitude, longitude, and name.

Personally, I think this design suffers from two additional flaws, though:

  1. You're keying your dictionary by some random object, [NSDate date]. That offers no value. You might as well make this an array.

  2. Your array of latitude, longitude, and the-name is ambiguous. Your data structure is highly contingent upon the order of items in the XML file. If you're going to use a dictionary for anything, I'd use it here.

So, bottom line, rather than a dictionary whose values are arrays, I'd flip that around. I'd suggest an array of dictionary objects. I think the parsedNearMe should be a NSMutableArray. And instead of a NSMutableArray called tempArray, you should use a NSMutableDictionary called tempDictionary:

First, define a few ivars:

NSMutableArray *parsedNearMe;         // this was a NSMutableDictionary
NSMutableDictionary *tempDictionary;

And then your NSXMLParserDelegate routines might look like:

- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    parsedNearMe = [[NSMutableArray alloc] init]; 
    currentString = nil;
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if ( [elementName isEqualToString:@"a-location"]) 
    {
        tempDictionary = [[NSMutableDictionary alloc] init];
    }
    else if ([elementName isEqualToString:@"latitude"] || 
             [elementName isEqualToString:@"longitude"] || 
             [elementName isEqualToString:@"the-name"]) 
    {
        currentString = [[NSMutableString alloc] init];
    }
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    [currentString appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"latitude"] ||
        [elementName isEqualToString:@"longitude"] ||
        [elementName isEqualToString:@"the-name"] )
    {
        [tempDictionary setObject:currentString forKey:elementName];
        currentString = nil;
    }
    else if ([elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe addObject:tempDictionary];
        tempDictionary = nil;
    }
}

Or, if you'd like to store latitude and longitude as NSNumber objects instead as strings, you could replace the didEndElement with:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"latitude"] ||
        [elementName isEqualToString:@"longitude"])
    {
        [tempDictionary setObject:@([currentString doubleValue]) forKey:elementName];
        currentString = nil;
    }
    else if ([elementName isEqualToString:@"the-name"] )
    {
        [tempDictionary setObject:currentString forKey:elementName];
        currentString = nil;
    }
    else if ([elementName isEqualToString:@"a-location"])
    {
        [parsedNearMe addObject:tempDictionary];
        tempDictionary = nil;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top