Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Interface 2, Advanced iOS
Mockup & Code Gen
($9.99)

Make your own iPhone apps
and run them live!
(free)

Pic Frame Dynamo: Photo Editing
($0.99)

Abiliator
($1.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Development

Reply
 
LinkBack Thread Tools Display Modes
Old 12-02-2010, 10:19 AM   #1 (permalink)
Registered Member
 
Join Date: May 2009
Posts: 78
tatebn is on a distinguished road
Default NSXMLParser problems

I have a significant amount of data that I have to load on first run of an application. I have it all in an xml file (2.5mb). I'm running through it using NSXMLParser. There are about 5 entities I'm interested in.

Here's a sample line of the xml.

Code:
<period weekStartDate="2002-04-26">
	<mortgageType type="FIXED" duration="30">
		<region code="US">
			<rate>6.88</rate>
			<points>0.7</points>
		</region>
		<region code="NE">
			<rate>6.90</rate>
			<points>0.7</points>
		</region>
		<region code="SE">
			<rate>6.85</rate>
			<points>0.7</points>
		</region>
		<region code="NC">
			<rate>6.95</rate>
			<points>0.5</points>
		</region>
		<region code="SW">
			<rate>6.89</rate>
			<points>0.7</points>
		</region>
		<region code="W">
			<rate>6.83</rate>
			<points>0.9</points>
		</region>
	</mortgageType>
	<mortgageType type="FIXED" duration="15">
		<region code="US">
			<rate>6.35</rate>
			<points>0.7</points>
		</region>
		<region code="NE">
			<rate>6.36</rate>
			<points>0.6</points>
		</region>
		<region code="SE">
			<rate>6.32</rate>
			<points>0.8</points>
		</region><region code="NC">
			<rate>6.41</rate>
			<points>0.4</points>
		</region>
		<region code="SW">
			<rate>6.40</rate>
			<points>0.7</points>
		</region>
		<region code="W">
			<rate>6.31</rate>
			<points>0.9</points>
		</region>
	</mortgageType>
	<mortgageType type="ARM" duration="5">
		<region code="US">
			<rate> </rate>
			<points> </points>
		</region>
		<region code="NE">
			<rate> </rate>
			<points> </points>
		</region>
		<region code="SE">
			<rate> </rate>
			<points> </points>
		</region>
		<region code="NC">
			<rate> </rate>
			<points> </points>
		</region>
		<region code="SW">
			<rate> </rate>
			<points> </points>
		</region>
		<region code="W">
			<rate> </rate>
			<points> </points>
		</region>
	</mortgageType>
	<mortgageType type="ARM" duration="1">
		<region code="US">
			<rate>4.91</rate>
			<points>0.7</points>
		</region>
		<region code="NE">
			<rate>5.05</rate>
			<points>0.7</points>
		</region>
		<region code="SE">
			<rate>4.90</rate>
			<points>0.9</points>
		</region>
		<region code="NC">
			<rate>5.04</rate>
			<points>0.6</points>
		</region>
		<region code="SW">
			<rate>4.88</rate>
			<points>0.7</points>
		</region>
		<region code="W">
			<rate>4.72</rate>
			<points>0.9</points>
		</region>
	</mortgageType>
</period>
I have about 1,500 lines like that right now. When I run this in the simulator it'll go through somewhere around 100 of them and then the app crashes. There is no error message and no memory warning thrown.

On the device it doesn't crash, but it's incredibly slow. About 2 seconds per line.

Here are the relevant delegate methods.

Code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
	attributes:(NSDictionary *)attributeDict {
	
	// Figure out the element type and create the necessary object.
	
	if ([elementName isEqualToString:@"period"]) {
		currentWeekStart = [dateFormatter dateFromString:[attributeDict objectForKey:@"weekStartDate"]];
		NSLog(@"parsing period %@", [dateFormatter stringFromDate:currentWeekStart]);
	}
	
	if ([elementName isEqualToString:@"mortgageType"]) {
		
		NSString *mType = [[attributeDict objectForKey:@"type"] autorelease];
		NSString *mDuration = [[attributeDict objectForKey:@"duration"] autorelease];
		
		// Make sure this is already in the db, otherwise it needs to be added
		if ([mortgageTypes objectForKey:mType] == nil 
			|| [[mortgageTypes objectForKey:mType] objectForKey:mDuration] == nil) {
			
			MortgageType *newMortgageType = [(MortgageType*)[NSEntityDescription
															insertNewObjectForEntityForName:@"MortgageType"
															inManagedObjectContext:[DataManager sharedManager].managedObjectContext] 
											 autorelease];
			
			newMortgageType.MortgageDuration = mDuration;
			newMortgageType.MortgageTypeCode = mType;
			[DataManager saveContext];
			
			// Refresh my list
			mortgageTypes = [MortgageType mortgageTypeDictionary];
		}
		
		// Start watching this one
		currentMortgageType = [[mortgageTypes objectForKey:mType] objectForKey:mDuration];
		
		
		
	}
	
	if ([elementName isEqualToString:@"region"]) {
		// Check to make sure the region exists, add if not
		NSString *regionCode = [attributeDict objectForKey:@"code"];
		if ([regions objectForKey:regionCode] == nil) {
			
			Region *newRegion = [(Region*)[NSEntityDescription
										  insertNewObjectForEntityForName:@"Region"
										  inManagedObjectContext:[DataManager sharedManager].managedObjectContext] 
								 autorelease];
			
			newRegion.RegionCode = regionCode;
			newRegion.Order = 0;
			
			[DataManager saveContext];
			
			// Refresh my list
			regions = [Region regionDictionary];
		}
		// Make current
		currentRegion = [regions objectForKey:regionCode];
		
		
		// Should have all the necessary info here to see if updating or adding.
		NSMutableDictionary *vars = [[[NSMutableDictionary alloc] init] autorelease];
		
		[vars setObject:currentWeekStart forKey:@"WeekBeginDay"];
		[vars setObject:currentMortgageType.MortgageTypeCode  forKey:@"MortgageTypeCode"];
		[vars setObject:currentMortgageType.MortgageDuration forKey:@"MortgageDuration"];
		[vars setObject:currentRegion.RegionCode forKey:@"RegionCode"];
		
		NSFetchRequest *fetchRequest =  [[[DataManager sharedManager].managedObjectModel
										  fetchRequestFromTemplateWithName:@"getIndividualSurveyResult"
										  substitutionVariables: vars] autorelease];
		
		// Should only be one.  Grab and update if that's the case.
		[fetchRequest setFetchLimit:1];
		
		NSError *error;
		NSArray *items = [[DataManager sharedManager].managedObjectContext
						  executeFetchRequest:fetchRequest error:&error];
		
		// If for some reason this one already exists, update it.
		if ([items count]) {
			currentSurveyResult = [[items objectAtIndex:0] retain];
		}
		else {
			currentSurveyResult = [(SurveyResult*)[NSEntityDescription
							insertNewObjectForEntityForName:@"SurveyResult"
							inManagedObjectContext:[DataManager sharedManager].managedObjectContext] retain];
			
			currentSurveyResult.WeekBeginDay = currentWeekStart;
		}

		
	}
	
	if ([elementName isEqualToString:@"rate"]) {
		
	}
	
	if ([elementName isEqualToString:@"points"]) {
		
	}
		
	
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	// Add to current string
	
	if (!currentString) {
        // currentString is an NSMutableString instance variable
        currentString = [[NSMutableString alloc] init];
    }
	
	[currentString appendString:string];
		
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	
	// Close object and save if necessary.
	
	// Do releases in here for mortgage type etc.
	
	if ([elementName isEqualToString:@"period"]) {
		[currentWeekStart release];
		currentWeekStart = nil;
	}
	
	if ([elementName isEqualToString:@"mortgageType"]) {
		[currentMortgageType release];
		currentMortgageType = nil;
		
	}
	
	if ([elementName isEqualToString:@"region"]) {
		
		
		// If the survey result and the mortgage type aren't connected, they need to be.
		if (!currentSurveyResult.MortgageType) {
			currentSurveyResult.MortgageType = currentMortgageType;
			
			// Add new SurveyResult to the related MortgageType
			[(MortgageType*)[[mortgageTypes objectForKey:currentMortgageType.MortgageTypeCode] 
							 objectForKey:currentMortgageType.MortgageDuration] addSurveyResultObject:currentSurveyResult];
		}
		
		// If the survey result and the region aren't connected, they need to be.
		if (!currentSurveyResult.Region) {
			currentSurveyResult.Region = currentRegion;
			
			// Add new SurveyResult to related Region
			[(Region*) [regions objectForKey:currentRegion.RegionCode] addSurveyResultObject:currentSurveyResult];
		}
		
		// Save current survey result and release
		// 
		
		// Save the db
		[DataManager saveContext];
		
		// Release the finished stuff
		[currentRegion release];
		currentRegion = nil;
		
		[currentSurveyResult release];
		currentSurveyResult = nil;
	}
	
	if ([elementName isEqualToString:@"rate"]) {
		if (currentSurveyResult) {
			if ([currentString length])
				currentSurveyResult.InterestRate = [NSDecimalNumber decimalNumberWithString:currentString];
		}
	}
	
	if ([elementName isEqualToString:@"points"]) {
		if (currentSurveyResult) {
			if ([currentString length]) 
				currentSurveyResult.Points = [NSDecimalNumber decimalNumberWithString:currentString];
			
			
		}
	}
	
	[currentString release];
	currentString = nil;
	
}
I need to figure out how to get this data in there either before or on the first run. Doesn't matter how it happens, but it needs to happen.

Could the problem be that I'm doing the core data save context for every result object I build?
If I create a managed object and fill it with pertinent data, then release it and create a new one before I save the database context, will the released managed object still be inserted when I save the db even though I don't released it through the variable?
tatebn is offline   Reply With Quote
Old 12-02-2010, 10:43 AM   #2 (permalink)
Registered Member
 
Join Date: Jan 2009
Posts: 60
substance21 is on a distinguished road
Default

You probably have two problems there: both speed and memory. Is the device actually getting through the whole file? I would guess it doesn't crash because it runs much slower than the simulator as it's much less common for the simulator to crash while the device runs fine (unless there are hardware dependencies).


Speed-wise, I think you've already hit on a probable cause; calling core data saves every time is pretty expensive.
You can verify using the CPU Sampler perf tool.


Memory-wise, you should be making your own autorelease pools. The default one doesn't get emptied out until the event loop runs again. NSXMLParser doesn't parse asynchronously, so the event loop is never run until it completes, and thus you just end up accumulating objects.



If you're just interested in having your app bootstrapped with a data set, I would recommend separately parsing the XML file and converting it into a sqlite database which you then ship with your app.
You can probably use the code you already have. Just fix it so it runs in the simulator (because it runs much faster than your device and all you need are the results), attach it to a sqlite persistence store, run it, then copy the resultant sqlite db into the resource of your app. Then in your app, attach the persistence store to the sqlite db located in your app's main bundle.
__________________
iPhoneApps:
enLegion - When everyone needs to be in the same place, but isn't
AutoWeb - Automate your web logins and browsing!
substance21 is offline   Reply With Quote
Old 12-02-2010, 10:51 AM   #3 (permalink)
Registered Member
 
Join Date: May 2009
Posts: 78
tatebn is on a distinguished road
Default

How would I go about attaching the bundled sqlite db to my core data model?
tatebn is offline   Reply With Quote
Reply

Bookmarks

Tags
core data, nsxmlparser, xml

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 367
6 members and 361 guests
chemistry, daudrizek, HemiMG, Kirkout, MarkC
Most users ever online was 1,387, 04-10-2012 at 04:21 AM.
» Stats
Members: 175,665
Threads: 94,120
Posts: 402,898
Top Poster: BrianSlick (7,990)
Welcome to our newest member, daudrizek
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 02:50 AM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0