How can I improve the XML parsing performance of my iOS code? - objective-c

This may have been asked a lot but I'm still lost. I need to parse an XML file that I retrieve from Google Reader's API. Basically, it contains objects such as below :
<object>
<string name="id">feed/http://developer.apple.com/news/rss/news.rss</string>
<string name="title">Apple Developer News</string>
<list name="categories">
<object>
<string name="id">user/17999068807557229152/label/Apple</string>
<string name="label">Apple</string>
</object>
</list>
<string name="sortid">DB67AFC7</string>
<number name="firstitemmsec">1317836072018</number>
<string name="htmlUrl">http://developer.apple.com/news/</string>
</object>
I have tried with NSXMLParser and it works but it is really slow. Maybe my code is not the most efficient but still, it can take more than 10 second to parse and save an object into Core Data. I also have taken a look a several other libraries but their use seem a bit complicated and heavy for such a small XML file.
What do you think I should use ?
Thank you.
EDIT
Here the parser code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:#"list"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"subscriptions"]){
subscriptionListFound = YES;
}
if(subscriptionListFound){
if([elementName isEqualToString:#"list"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"categories"]){
categoryFound = YES;
currentCategoryId = [[[NSMutableString alloc] init] autorelease];
currentCategoryLabel = [[[NSMutableString alloc] init] autorelease];
}
if([elementName isEqualToString:#"object"] && !subscriptionFound && !categoryFound){
subscriptionFound = YES;
currentSubscriptionTitle = [[[NSMutableString alloc] init] autorelease];
currentSubscriptionId = [[[NSMutableString alloc] init] autorelease];
currentSubscriptionHtmlURL = [[[NSMutableString alloc] init] autorelease];
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"id"]){
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"title"]){
subscriptionTitleFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"label"]){
categoryLabelFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"htmlUrl"]){
subscriptionHtmlURLFound = YES;
}
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"list"] && !categoryFound){
subscriptionListFound = NO;
}
if([elementName isEqualToString:#"list"] && categoryFound){
categoryFound = NO;
}
if([elementName isEqualToString:#"object"] && !categoryFound && subscriptionFound){
[self saveSubscription];
[[NSNotificationCenter defaultCenter] postNotificationName:#"currentSubscriptionNotification" object:currentSubscriptionTitle];
subscriptionFound = NO;
}
if([elementName isEqualToString:#"string"]){
if(subscriptionIdFound == YES) {
[currentSubscriptionId appendString:self.currentParsedCharacterData];
subscriptionIdFound = NO;
}
if(subscriptionTitleFound == YES) {
[currentSubscriptionTitle appendString:self.currentParsedCharacterData];
subscriptionTitleFound = NO;
}
if(subscriptionHtmlURLFound == YES) {
[currentSubscriptionHtmlURL appendString:self.currentParsedCharacterData];
subscriptionHtmlURLFound = NO;
}
if(categoryIdFound == YES) {
[currentCategoryId appendString:self.currentParsedCharacterData];
categoryIdFound = NO;
}
if(categoryLabelFound == YES) {
[currentCategoryLabel appendString:self.currentParsedCharacterData];
categoryLabelFound = NO;
}
}
[self.currentParsedCharacterData setString:#""];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self.currentParsedCharacterData appendString:string];
}
Here the code to save by means of CoreData:
- (void) saveSubscription {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:
[NSEntityDescription entityForName:#"Group" inManagedObjectContext:context]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"(id == %#)",self.currentCategoryId]];
[fetchRequest setSortDescriptors: [NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey: #"id"
ascending:YES] autorelease]]];
NSError *error2 = nil;
NSArray *foundGroups = [context executeFetchRequest:fetchRequest error:&error2];
if ([foundGroups count] > 0) {
self.currentGroupObject = [foundGroups objectAtIndex:0];
}
else {
self.currentGroupObject = [NSEntityDescription insertNewObjectForEntityForName:#"Group" inManagedObjectContext:context];
[self.currentGroupObject setId:self.currentCategoryId];
[self.currentGroupObject setLabel:self.currentCategoryLabel];
}
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:
[NSEntityDescription entityForName:#"Subscription" inManagedObjectContext:context]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"(id == %#)", self.currentSubscriptionId]];
[fetchRequest setSortDescriptors: [NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey: #"id"
ascending:YES] autorelease]]];
error2 = nil;
NSArray *foundSubscriptions = [context executeFetchRequest:fetchRequest error:&error2];
if ([foundSubscriptions count] > 0) {
self.currentSubscriptionObject = [foundSubscriptions objectAtIndex:0];
}
else {
self.currentSubscriptionObject = [NSEntityDescription insertNewObjectForEntityForName:#"Subscription" inManagedObjectContext:context];
[self.currentSubscriptionObject setId:self.currentSubscriptionId];
[self.currentSubscriptionObject setTitle:self.currentSubscriptionTitle];
[self.currentSubscriptionObject setHtmlURL:self.currentSubscriptionHtmlURL];
NSString *faviconURL = [self favIconUrlStringFromURL:self.currentSubscriptionHtmlURL];
NSString *faviconPath = [self saveFavicon:self.currentSubscriptionTitle url:faviconURL];
[self.currentSubscriptionObject setFaviconPath:faviconPath];
[self.currentSubscriptionObject setGroup:self.currentGroupObject];
[self.currentGroupObject addSubscriptionObject:self.currentSubscriptionObject];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}

Your parsing logic is quite inefficient - you are doing the same test over and over again by saying
if (string and x) do this
if (string and y) do this
if (string and z) do this
Instead of
if (string)
if (x) do this
if (y) do this
if (z) do this
All those unnecessary string comparisons are probably why your parsing is so slow. Same goes for all the object lookups. If you need a value multiple times, get it once and then store it in a variable.
Objective C method calls are relatively slow and can't be optimised away by the compiler, so if the value doesn't change you should call the method once and then store it.
So for example, this:
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"id"]){
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"title"]){
subscriptionTitleFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"label"]){
categoryLabelFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"htmlUrl"]){
subscriptionHtmlURLFound = YES;
}
Could be rewritten as this:
NSString *name = [attributeDict objectForKey:#"name"];
if([elementName isEqualToString:#"string"])
{
if ([name isEqualToString:#"id"])
{
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
else if ([name isEqualToString:#"title"])
{
subscriptionTitleFound = YES;
}
else if ([name isEqualToString:#"label"])
{
categoryLabelFound = YES;
}
else if ([name isEqualToString:#"htmlUrl"])
{
subscriptionHtmlURLFound = YES;
}
}
Which is way more efficient.

I suggest you to use GDataXML. It's quite simple to use and very fast. For further info you can read at how-to-read-and-write-xml-documents-with-gdataxml.
I've already replied to a similar question on how to read attribute with GDataXML in this Stack Overflow topic: get-xml-response-value-with-gdataxml.

I my opinion, the best library for parsing XML on iOS is TouchXML. It allows you to parse XML using xPaths and has advanced element parsing options. You can also parse XHTML documents with this.
Parsing is very easy:
NSData *xmlData = read your xml file
CXMLDocument *doc = [[CXMLDocument alloc] initWithData:xmlData options:0 error:nil]
NSArray *objects = [doc nodesForXPath:#"//object" error:nil];
for (CXMLElement *object in objects) {
NSArray *children = [object children];
for(CXMLElement *child in children) {
if([[child name] isEqualToString:#"string"]) {
// you are parsing <string> element.
// you can obtain element attribute by:
NSString *name = [[child attributeForName:#"name"] stringValue];
// you can obtain string between <></> tags via:
NSString *value = [child stringValue];
} else if([[child name] isEqualToString:#"list"]) {
// you are parsing <list> element.
} else if ...
}
}

After having developed a few apps with similar needs as yours, I would wholeheartedly recommend the AQToolkit
My usual setup for parsing XML is more or less like this:
Create a separate queue, using either GCD og NSOperationsQueue
Set up a input stream using HTTPMessage and AQGZipInputStream
Example Code:
HTTPMessage *message = [HTTPMessage requestMessageWithMethod:#"GET" url:url version:HTTPVersion1_1];
[message setUseGzipEncoding:YES];
AQGzipInputStream *inputstream = [[AQGzipInputStream alloc] initWithCompressedStream: [message inputStream]];
Hand the stream to a separate parser delegate, which creates a separate NSManagedObjectContext, and merges changes into main NSManagedObjectContext on save (NSManagedObject is not thread safe!)
Example code for initializing the context, and adding notifications for merging:
-(void)parserDidStartDocument:(AQXMLParser *)parser
{
self.ctx=[[NSManagedObjectContext alloc] init];
[self.ctx setMergePolicy: NSMergeByPropertyObjectTrumpMergePolicy];
[self.ctx setPersistentStoreCoordinator: [Database db].persistentStoreCoordinator];
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:#selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:self.ctx];
parsedElements = 0;
}
- (void)mergeContextChanges:(NSNotification *)notification{
SEL selector = #selector(mergeHelper:);
[self performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}
- (void)mergeHelper:(NSNotification*)saveNotification
{
// Fault in all updated objects
NSArray* updates = [[saveNotification.userInfo objectForKey:#"updated"] allObjects];
for (NSInteger i = [updates count]-1; i >= 0; i--)
{
[[[Database db].managedObjectContext objectWithID:[[updates objectAtIndex:i] objectID]] willAccessValueForKey:nil];
}
// Merge
[[Database db].managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
}
In my mind, choosing the right parser is more critical for huge datasets. If your dataset is manageable, then you have a lot to gain from a decent implementation. Using any libxml based parser, and parsing chunks of data as you receive them will give you significant performance increases from parsing data after it is downloaded.
Depending on your datasource, libz might throw Z_BUF_ERROR (at least in the simulator). I've suggested a solution in a pull-request on the AQToolkit, but I'm quite sure there would be even better solutions out there!

Related

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;
}
}
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.

Doesn't geting data in right form from a 3 level xml service when using NSXMLParser class

I have to parse a 3 level xml which showing below:-
<Navigation>
<parent>
<parentheader>
<![CDATA[ Home ]]>
</parentheader>
<url>my-profile</url>
<Content>...</Content>
</parent>
<parent>
<parentheader>
<![CDATA[ Exhibiton ]]>
</parentheader>
<child>
<childheader>
<![CDATA[ London Exhibition ]]>
</childheader>
<subchild>London Sub
<url>ezone</url>
<Content>...</Content>
</subchild>
</child>
<child>
<childheader>
<![CDATA[ Asia Exhibition ]]>
</childheader>
<url>exhibition-asia-tour</url>
<Content>...</Content>
</child>
</parent>
</Navigation>
i am implementing the NSXMLParser class and delegates method below is the code:-
.h File
#interface NavigationXMLParser : NSObject<NSXMLParserDelegate>
{
NSXMLParser *xmlParser;
NSMutableDictionary *item,*childDict,*subChildDict;
NSMutableArray *nodesArr,*childArr,*subChildArr;
NSMutableString *parent, *url,*child,*subchild;
NSString *currentElement;
BOOL childBool;
}
-(void) fetchXMLData;
#end
.m implementation file code:-
#implementation NavigationXMLParser
-(void) fetchXMLData
{
xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://exhibitors.gastechkorea.com/admin/XMl_APP_Navigation.aspx"]];
[xmlParser setDelegate:self];
[xmlParser setShouldResolveExternalEntities:NO];
[xmlParser setShouldProcessNamespaces:NO];
[xmlParser setShouldReportNamespacePrefixes:NO];
[xmlParser parse];
}
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
nodesArr =[[NSMutableArray alloc] init];
//[sharedSQLiteObj createFavoriteAgendaTableNamed:#"" withField1:#"" withField2:#"" withField3:#"" withField4:#""];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSString * errorString = [NSString stringWithFormat:#"Unable to download story feed from web site (Error code %i )", [parseError code]];
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:#"Error loading content" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
{
currentElement = [elementName copy];
if ([elementName isEqualToString:#"parent"])
{
item = [[NSMutableDictionary alloc] init];
parent = [[NSMutableString alloc]init];
url = [[NSMutableString alloc]init];
}
else if ([elementName isEqualToString:#"child"])
{
child = [[NSMutableString alloc]init];
childArr = [[NSMutableArray alloc] init];
childDict = [[NSMutableDictionary alloc] init];
url = [[NSMutableString alloc]init];
}
else if ([elementName isEqualToString:#"subchild"])
{
subchild = [[NSMutableString alloc]init];
subChildArr = [[NSMutableArray alloc] init];
subChildDict = [[NSMutableDictionary alloc] init];
url = [[NSMutableString alloc]init];
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([currentElement isEqualToString:#"parentheader"])
{
[parent appendString:string];
}
else if ([currentElement isEqualToString:#"url"])
{
[url appendString:string];
if (!(child.length ==0))
{
if (subchild.length==0)
{
[childDict setObject:string forKey:#"url"];
}
else{
[subChildDict setObject:string forKey:#"url"];
}
}
}
else if ([currentElement isEqualToString:#"childheader"])
{
[child appendString:string];
if (!(string.length ==0))
{
[childDict setObject:string forKey:#"childheader"];
}
}
else if ([currentElement isEqualToString:#"subchild"])
{
[subchild appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
if (!([string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length ==0))
{
[subChildDict setObject:string forKey:#"subchild"];
[subChildArr addObject:[subChildDict copy]];
[childDict setObject:subChildArr forKey:#"subchild"];
subChildDict = nil;
};
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"child"])
{
if (!(childDict.count ==0)) {
[childArr addObject:[childDict copy]];
childDict = nil;
}
}
if ([elementName isEqualToString:#"parent"])
{
[item setObject:[parent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"parent"];
[item setObject:[url stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"url"];
if (!(childArr.count ==0))
{
[item setObject:childArr forKey:#"child"];
}
else
{
[item setObject:#"" forKey:#"child"];
}
//[item setObject:[subchild stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"subchild"];
[nodesArr addObject:[item copy]];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(#"nodesArr---%#",nodesArr);
}
#end
i got the responsed array from xmlparser class:-
(
{
child = "";
parent = Home;
url = "my-profile";
},
{
child = (
{
childheader = "\n";
}
);
parent = Exhibiton;
url = "exhibition-asia-tour";
},
{
child = (
{
childheader = "\n";
}
);
parent = Calender;
url = "http://www.google.com";
}
)
i am not getting the data in right structure i am wrong some where but didn't find the solution.
i want to get data in the below structure:--
(
{ parent="…."
child=""
url="……"
content="…."
}
{parent ="……"
child = ({ child="……";
subchild= ({
name= "….."
url="….."
content="….."
}
{
…………………….
…..……………..
})
}
{
child="……"
………………
})
)
}
Thanks in advace for your help!!!
it's all about how to make use of the NSXMLParserDelegate methods.
Your XML file can provide you the following informations:
The characters between [CDATA], and the characters between elements.
So you need to implement the following Delegate methods to retrive the informations from the XML file:
This method will get you every string between the [CDATA]:
-(void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
NSMutableString *str=[[NSMutableString alloc]initWithData:CDATABlock encoding:NSUTF8StringEncoding];
[SomeArray addObject:str];
}
And this method will get you every string between elements:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// here you need to alloc your string
NSMutableString *parent = [[NSMutableString alloc]init];
[parent appendString:string];
[parent release];
}
after you retrive your data in NSMutableArray and NSMutableString, you can filter your data as you need. But thats how to Parse your XML file.
It's just not that complicated. Good luck ^_^

save records from WCF service in CoreData

I am new to iOS development and CoreData too. I am calling a .Net WCF service for displaying data in a UITableViewcontroller in my app.I am saving this data in CoreData. When I add a new record on the server,I want it to get displayed in the UITableViewController as well as saved in CoreData.But this doesnt happen.I have to do a "Reset Contents and Settings" on the Simulator and then run the application again. When I do this,the app displays the latest records from the service.It also saves the new record in CoreData.I am using SUDZC for interacting with the wcf service.The code for calling the service,displaying data in UITableViewController and saving it to CoreData looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
[my_table setDataSource:self];
[my_table setDelegate:self];
EDViPadDocSyncService *service = [[EDViPadDocSyncService alloc]init];
[service getAllCategories:self action:#selector(handleGetAllCategories:)];
}
-(void)handleGetAllCategories:(id)value
{
if([value isKindOfClass:[NSError class]])
{
NSLog(#"This is an error %#",value);
return;
}
if([value isKindOfClass:[SoapFault class]])
{
NSLog(#"this is a soap fault %#",value);
return;
}
NSMutableArray *result = (NSMutableArray*)value;
self.myData = [[NSMutableArray array] init];//array for storing 'category name'
self.catId = [[NSMutableArray array]init];//array for storing 'category ID'
self.myData=[self getCategories];
/*store data in Core Data - START*/
NSMutableArray *coreDataCategoryarray = [[NSMutableArray alloc]init];
NSManagedObjectContext *context = [self managedObjectContext];
Categories *newCategory;//this is the CoreData 'Category' object
for(int j=0;j<[result count];j++)
{
EDVCategory *edvCat = [[EDVCategory alloc]init];//this is the SUDZC 'Category' object
edvCat = [result objectAtIndex:j];
if ([self.catId count]>0) {
for (int i=0; i<[self.catId count]; i++) {
if ([edvCat categoryId] == [[self.catId objectAtIndex:i] integerValue]) {
checkFlag=TRUE;
}
}
}
if (checkFlag == FALSE) {
newCategory = [NSEntityDescription insertNewObjectForEntityForName:#"Categories" inManagedObjectContext:context];
[newCategory setCategoryId:[NSNumber numberWithInt:[edvCat categoryId]]];
[newCategory setCategoryName:edvCat.categoryName];
[newCategory setDocCount:[NSNumber numberWithInt:[edvCat docCount]]];
[newCategory setCategoryType:[NSNumber numberWithShort:[edvCat categoryType]]];
[newCategory setSubCategoryId:[NSNumber numberWithInt:[edvCat subCategoryId]]];
[coreDataCategoryarray addObject:newCategory];
}
}
/*store data in Core Data - END*/
NSError *error = nil;
if (![context save:&error])
{
[coreDataCategoryarray release];
}
else
{
//return [coreDataCategoryarray autorelease];
[coreDataCategoryarray autorelease];
}
self.myData=[self getCategories];
[my_table reloadData];
}
-(NSMutableArray *)getCategories
{
NSFetchRequest *request = [[[NSFetchRequest alloc] init]autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Categories" inManagedObjectContext:__managedObjectContext];
NSSortDescriptor *sortByName = [[[NSSortDescriptor alloc] initWithKey:#"categoryId" ascending:YES] autorelease];
[request setSortDescriptors:[NSArray arrayWithObject:sortByName]];
[request setEntity:entity];
entity = nil;
NSError *error = nil;
NSMutableArray *fetchResults = [[__managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[request setReturnsObjectsAsFaults:NO];
NSManagedObject *aTabrss;
NSMutableArray *arForGetCategory=[[NSMutableArray alloc]init];
for (aTabrss in fetchResults){
[arForGetCategory addObject:[aTabrss valueForKey:#"categoryName"]];
[self.catId addObject:[aTabrss valueForKey:#"categoryId"]];
}
return (arForGetCategory);
}
What changes should I make in my code so that it reflects the latest data from the service and saves it to CoreData(sqlite) at the same time?
It seems like the class that you really need is NSUserDefaults:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsuserdefaults_Class/Reference/Reference.html

nsmutablearray elements to nsstring

I want to retrieve elements that are parsed in a NSMutableArray and store them into a NSString variable and then store them in NSMutableArray as NSString (because I want to display the content in a NSComboBox). I tried this but it dosen't work. Can you fix the problem, I can't fix it:
//--this is the parsing code :
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"user"]) {
NSLog(#"user element found – create a new instance of User class...");
if(currentElementValue == nil)
currentElementValue = [NSMutableString string];
else
[currentElementValue setString:#""];
}
else {
currentElementValue = nil;
}
user = [[User alloc] init];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentElementValue) {
// init the ad hoc string with the value
currentElementValue = [[NSMutableString alloc] initWithString:string];
} else {
// append value to the ad hoc string
[currentElementValue appendString:string];
if (currentElementValue)
{
currentElementValue = nil;
}
}
NSLog(#"Processing value for : %#", string);
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"users"]) {
// We reached the end of the XML document
return;
NSLog(#"QUIT");
}
if ([elementName isEqualToString:#"userName"]) {
[[self user] setUserName:currentElementValue];
NSLog(#"final step for value: %#", user.userName);
NSLog(#"currentElementName content : %#", currentElementValue);
[currentElementValue release];
NSLog(#"release : %#", currentElementValue);
currentElementValue = nil;
NSLog(#"release : %#", currentElementValue);
}
if ([elementName isEqualToString:#"firstName"]) {
[[self user] setFirstName:currentElementValue];
[currentElementValue release];
currentElementValue = nil;
}
if ([elementName isEqualToString:#"lastName"]) {
[[self user] setLastName:currentElementValue];
[currentElementValue release];
currentElementValue = nil;
}
if ([elementName isEqualToString:#"user"]) {
NSLog(#"\n user=%# \n",user);
[users addObject:user];
NSLog(#"userName test : %#", users);
[user release];
user = nil;
}
}
-(BOOL)parseDocumentWithData:(NSData *)data {
if (data == nil)
return NO;
NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data];
[xmlparser setDelegate:self];
[xmlparser setShouldResolveExternalEntities:NO];
BOOL ok = [xmlparser parse];
if (ok == NO)
NSLog(#"error");
else
NSLog(#"OK");
[xmlparser release];
return ok;
}
// this is the xml file :
<users>
<user>
<userName>mspeller</userName>
<firstName>Mike</firstName>
<lastName>Speller</lastName>
</user>
<user>
<userName>mgdan</userName>
<firstName>Mila</firstName>
<lastName>Gdan</lastName>
</user>
</users>
//-------
NSMutableArray *tabletest= [[NSMutableArray alloc] init];
NSMutableString * result = [[NSMutableString alloc] init];
int i;
for(i=0; i < [users count]; i++){
[result appendString:[NSString stringWithFormat:#"%#",[[users objectAtIndex:i] valueForKey:#"userName"]] ];
NSLog(#"result==%#",result);
[tabletest addObject:result];
}
Based on your link in the comment section I think you're accessing the "userName" property the wrong way. You're trying to access it, as users contains NSDictionary objects. As far as I can see you're adding User objects to the NSMutableArray.
Try the following (I took the liberty to beautify the code a bit):
NSMutableArray *tabletest= [NSMutableArray array];
for (User* user in users)
{
NSString* result = [NSString stringWithFormat:#"%#", user.userName];
NSLog(#"result==%#",result);
[tabletest addObject:result];
}
Please correct me if I totally misunderstood your design.
I don't follow what your intention is, but what your code does at the moment is add the same string [user count] time to the array tabletest as follows:
The line:
[result appendString:[NSString stringWithFormat:#"%#",[[users objectAtIndex:i] valueForKey:#"userName"]] ];
accumulates into result the result of appending each [[users objectAtIndex:i] valueForKey:#"userName"] together - each iteration of the loop adds the next item to the end of result.
The line:
[tabletest addObject:result];
Adds the object referenced by result into the array. This is done once per iteration so the array ends up with [users count] references to the same object. Placing a reference to a mutable string into an array does not place a copy of its current value, just a reference to the string - mutate the string and the mutation is visible through the reference stored in the array.
So the final result of your code is an array of [users count] references to the same mutable string, and that string is the concatenation of all the [[users objectAtIndex:i] valueForKey:#"userName"] values.
What was your intent?
If you are trying to create an array of string representations of [[users objectAtIndex:i] valueForKey:#"userName"] then change the code to:
NSMutableArray *tabletest= [[NSMutableArray alloc] init];
for(int i = 0; i < [users count]; i++)
{
// create a string representation of userName
NSString *result = [NSString stringWithFormat:#"%#",[[users objectAtIndex:i] objectForKey:#"userName"]];
// add the string to the array
[tabletest addObject:result];
}
But maybe your intent is something else?

Memory buildup when parsing XML into a Core Data store using NSXMLParser

I have a problem with an app that takes an XML feed, parses it, and stores the results into Core Data.
The problem only occurs on the very first run of the app when there is nothing in the store and the whole feed is parsed and stored.
The problem is simply that memory allocations build up and up until, on 50% of attempts it crashes the app, usually at around 10Mb.
The objects allocated seem to be CFData(store) objects and I can't seem to find any way to force a release of them.
If you can get it to run once and successfully complete the parsing and save to core data then every subsequent launch is fine, memory usage never exceeds 2.5Mb
Here's the general approach I have before we get into code:
Get the feed into an NSData object. Use NSFileManager to store it as a file.
Create a URL from the file path and give it to the parseXMLFile: method. Delegate is self.
On reaching parser:didStartElement:namespaceURI:qualifiedName:attributes: I create a dictionary to catch data from tags I need.
The parser:foundCharacters: method saves the contents of the tag to a string
The parser:didEndElement:... method then determines if the tag is something we need. If so it adds it to the dictionary if not it ignores it. Once it reaches the end of an item it calls a method to add it to the core data store.
From looking at sample code and other peoples postings here it seems there's nothing in the general approach thats wrong.
The code is below, it comes from a larger context of a view controller but I omitted anything not directly related.
In terms of things I have tried:
The feed is XML, no option to convert to JSON, sorry.
It's not the images being found and stored in the shared documents area.
Clues as to what it might be:
This is the entry I get from Instruments on the largest most numerous things allocated (256k per item):
Object Address Category Creation Time Live Size Responsible
Library Responsible Caller
1 0xd710000 CFData
(store) 16024774912 • 262144 CFNetwork URLConnectionClient::clientDidReceiveData(_CFData
const*,
URLConnectionClient::ClientConnectionEventQueue*)
And here's the code, edited for anonymity of the content ;)
-(void)parserDidStartDocument:(NSXMLParser *)feedParser { }
-(void)parserDidEndDocument:(NSXMLParser *)feedParser { }
-(void)parser:(NSXMLParser *)feedParser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
eachElement = elementName;
if ( [eachElement isEqualToString:#"item" ] )
{
//create a dictionary to store each item from the XML feed
self.currentItem = [[[NSMutableDictionary alloc] init] autorelease];
}
}
-(void)parser:(NSXMLParser *)feedParser foundCharacters:(NSString *)feedString
{
//Make sure that tag content doesn't line break unnecessarily
if (self.currentString == nil)
{
self.currentString = [[[NSMutableString alloc] init]autorelease];
}
[self.currentString appendString:feedString];
}
-(void)parser:(NSXMLParser *)feedParser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
eachElement = elementName;
NSString *tempString = [self.currentString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ( [eachElement isEqualToString:#"title"] )
{
//skip the intro title
if (![tempString isEqualToString:#"Andy Panda UK"])
{
//add item to di citonary output to console
[self.currentItem setValue:tempString forKey:eachElement];
}
}
if ( [eachElement isEqualToString:#"link"] )
{
//skip the intro link
if (![tempString isEqualToString:#"http://andypanda.co.uk/comic"])
{
//add item to dicitonary output to console
[self.currentItem setValue:tempString forKey:eachElement];
}
}
if ( [eachElement isEqualToString:#"pubDate"] )
{
//add item to dicitonary output to console
[self.currentItem setValue:tempString forKey:eachElement];
}
if ( [eachElement isEqualToString:#"description"] )
{
if ([tempString length] > 150)
{
//trims the string to just give us the link to the image file
NSRange firstPart = [tempString rangeOfString:#"src"];
NSString *firstString = [tempString substringFromIndex:firstPart.location+5];
NSString *secondString;
NSString *separatorString = #"\"";
NSScanner *aScanner = [NSScanner scannerWithString:firstString];
[aScanner scanUpToString:separatorString intoString:&secondString];
//trims the string further to give us just the credits
NSRange secondPart = [firstString rangeOfString:#"title="];
NSString *thirdString = [firstString substringFromIndex:secondPart.location+7];
thirdString = [thirdString substringToIndex:[thirdString length] - 12];
NSString *fourthString= [secondString substringFromIndex:35];
//add the items to the dictionary and output to console
[self.currentItem setValue:secondString forKey:#"imageURL"];
[self.currentItem setValue:thirdString forKey:#"credits"];
[self.currentItem setValue:fourthString forKey:#"imagePreviewURL"];
//safety sake set unneeded objects to nil before scope rules kick in to release them
firstString = nil;
secondString = nil;
thirdString = nil;
}
tempString = nil;
}
//close the feed and release all the little objects! Fly be free!
if ( [eachElement isEqualToString:#"item" ] )
{
//get the date sorted
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"EEE, dd MMM yyyy HH:mm:ss ZZZZ";
NSDate *pubDate = [formatter dateFromString:[currentItem valueForKey:#"pubDate"]];
[formatter release];
NSArray *fetchedArray = [[NSArray alloc] initWithArray:[fetchedResultsController fetchedObjects]];
//build the string to make the image
NSString *previewURL = [#"http://andypanda.co.uk/comic/comics-rss/" stringByAppendingString:[self.currentItem valueForKey:#"imagePreviewURL"]];
if ([fetchedArray count] == 0)
{
[self sendToCoreDataStoreWithDate:pubDate andPreview:previewURL];
}
else
{
int i, matches = 0;
for (i = 0; i < [fetchedArray count]; i++)
{
if ([[self.currentItem valueForKey:#"title"] isEqualToString:[[fetchedArray objectAtIndex:i] valueForKey:#"stripTitle"]])
{
matches++;
}
}
if (matches == 0)
{
[self sendToCoreDataStoreWithDate:pubDate andPreview:previewURL];
}
}
previewURL = nil;
[previewURL release];
[fetchedArray release];
}
self.currentString = nil;
}
- (void)sendToCoreDataStoreWithDate:(NSDate*)pubDate andPreview:(NSString*)previewURL {
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
[newManagedObject setValue:[[currentItem valueForKey:#"title"] description] forKey:#"stripTitle"];
[newManagedObject setValue:[[currentItem valueForKey:#"credits"] description] forKey:#"stripCredits"];
[newManagedObject setValue:[[currentItem valueForKey:#"imageURL"] description] forKey:#"stripImgURL"];
[newManagedObject setValue:[[currentItem valueForKey:#"link"] description] forKey:#"stripURL"];
[newManagedObject setValue:pubDate forKey:#"stripPubDate"];
[newManagedObject setValue:#"NO" forKey:#"stripBookmark"];
//**THE NEW SYSTEM**
NSString *destinationPath = [(AndyPadAppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory];
NSString *guidPreview = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *guidImage = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *dpPreview = [destinationPath stringByAppendingPathComponent:guidPreview];
NSString *dpImage = [destinationPath stringByAppendingPathComponent:guidImage];
NSData *previewD = [NSData dataWithContentsOfURL:[NSURL URLWithString:previewURL]];
NSData *imageD = [NSData dataWithContentsOfURL:[NSURL URLWithString:[currentItem valueForKey:#"imageURL"]]];
//NSError *error = nil;
[[NSFileManager defaultManager] createFileAtPath:dpPreview contents:previewD attributes:nil];
[[NSFileManager defaultManager] createFileAtPath:dpImage contents:imageD attributes:nil];
//TODO: BETTER ERROR HANDLING WHEN COMPLETED APP
[newManagedObject setValue:dpPreview forKey:#"stripLocalPreviewPath"];
[newManagedObject setValue:dpImage forKey:#"stripLocalImagePath"];
[newManagedObject release];
before++;
[self.currentItem removeAllObjects];
self.currentItem = nil;
[self.currentItem release];
[xmlParserPool drain];
self.xmlParserPool = [[NSAutoreleasePool alloc] init];
}
[EDIT]
#Rog
I tried playing with NSOperation but no luck so far, the same me,ory build up with the same items, all about 256k each. Here's the code:
- (void)sendToCoreDataStoreWithDate:(NSDate*)pubDate andPreview:(NSString*)previewURL
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
[newManagedObject setValue:[[currentItem valueForKey:#"title"] description] forKey:#"stripTitle"];
[newManagedObject setValue:[[currentItem valueForKey:#"credits"] description] forKey:#"stripCredits"];
[newManagedObject setValue:[[currentItem valueForKey:#"imageURL"] description] forKey:#"stripImgURL"];
[newManagedObject setValue:[[currentItem valueForKey:#"link"] description] forKey:#"stripURL"];
[newManagedObject setValue:pubDate forKey:#"stripPubDate"];
[newManagedObject setValue:#"NO" forKey:#"stripBookmark"];
NSString *destinationPath = [(AndyPadAppDelegate *)[[UIApplication sharedApplication] delegate] applicationDocumentsDirectory];
NSString *guidPreview = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *guidImage = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *dpPreview = [destinationPath stringByAppendingPathComponent:guidPreview];
NSString *dpImage = [destinationPath stringByAppendingPathComponent:guidImage];
//Create an array and send the contents off to be dispatched to an operation queue
NSArray *previewArray = [NSArray arrayWithObjects:dpPreview, previewURL, nil];
NSOperation *prevOp = [self taskWithData:previewArray];
[prevOp start];
//Create an array and send the contents off to be dispatched to an operation queue
NSArray *imageArray = [NSArray arrayWithObjects:dpImage, [currentItem valueForKey:#"imageURL"], nil];
NSOperation *imagOp = [self taskWithData:imageArray];
[imagOp start];
[newManagedObject setValue:dpPreview forKey:#"stripLocalPreviewPath"];
[newManagedObject setValue:dpImage forKey:#"stripLocalImagePath"];
//[newManagedObject release]; **recommended by stackoverflow answer
before++;
[self.currentItem removeAllObjects];
self.currentItem = nil;
[self.currentItem release];
[xmlParserPool drain];
self.xmlParserPool = [[NSAutoreleasePool alloc] init];
}
- (NSOperation*)taskWithData:(id)data
{
NSInvocationOperation* theOp = [[[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(threadedImageDownload:)
object:data] autorelease];
return theOp;
}
- (void)threadedImageDownload:(id)data
{
NSURL *urlFromArray1 = [NSURL URLWithString:[data objectAtIndex:1]];
NSString *stringFromArray0 = [NSString stringWithString:[data objectAtIndex:0]];
NSData *imageFile = [NSData dataWithContentsOfURL:urlFromArray1];
[[NSFileManager defaultManager] createFileAtPath:stringFromArray0
contents:imageFile
attributes:nil];
//-=====0jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjmn **OLEG**
}
You can disable the caching :
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
Or clear it :
[[NSURLCache sharedURLCache] removeAllCachedResponses];
This should fix your problem.
Take a look at AQXMLParser. There is a example app that shows how to integrate it with Core Data on a background thread. It's a streaming parser so memory usage is minimal.
Set up a second MOC and fetched results controller for the UI and refetch when the parsing is complete to get the new data. (one option is to register for change notifications when the background MOC saves to merge between contexts)
For images, there are a number of third party categories on UIImageVew that support asynchronous downloads and placeholder images. So you would parse the image URL, store it in Core Data, then set the URL on the image view when that item is viewed. This way the user doesn't need to wait for all the images to be downloaded.
Your problem is here:
[newManagedObject release];
Get rid of it, and the crashes will be gone (you don't release NSManagedObjects, Coredata will handle that for you).
Cheers,
Rog