NSXMLParser does not parse last node (possible memory issue) - objective-c

I have the following .xml file :
<?xml version="1.0" encoding="UTF-8"?>
<company>
<employee>
<id>0</id>
<firstname>Jack</firstname>
<lastname>Johnson</lastname>
<jobtitle>CEO</jobtitle>
<departmentid>0</departmentid>
<parentid>0</parentid>
</employee>
<employee>
<id>1</id>
<firstname>Mik</firstname>
<lastname>Black</lastname>
<jobtitle>Senior Manager</jobtitle>
<departmentid>0</departmentid>
<parentid>0</parentid>
</employee>
<employee>
<id>2</id>
<firstname>Kim<firstname>
<lastname>Friht</lastname>
<jobtitle>Senior Manager</jobtitle>
<departmentid>0</departmentid>
<parentid>0</parentid>
</employee>
...
The following header file:
#import <Foundation/Foundation.h>
#import "Employee.h"
#interface IdParser : NSObject <NSXMLParserDelegate> {
NSXMLParser *xmlParser;
NSMutableArray *employees;
NSString *currentElement;
Employee *employee;
NSMutableString *tempId, *tempFirstName, *tempLastName, *tempDeptId, *tempJobTitle, *tempParentId;
}
-(NSMutableArray *)getSubordinates:(int)idNumber;
#end
And the following implementation:
#import "IdParser.h"
#import "Employee.h"
#implementation IdParser
- (void)start{
NSString *file = #"/users/localadmin/Desktop/employeeData.xml";
NSFileManager *filemgr = [NSFileManager defaultManager];
NSData *dataBuffer = [filemgr contentsAtPath: file];
xmlParser = [[NSXMLParser alloc] initWithData:dataBuffer];
[xmlParser setDelegate:self];
[xmlParser parse];
}
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict{
[currentElement release];
currentElement = [elementName copy];
if([elementName isEqualToString:#"employee"]){
employee = [[Employee alloc]init];
}
if([elementName isEqualToString:#"id"]){
tempId = [[NSMutableString alloc]init ];
}
if([elementName isEqualToString:#"firstname"]){
tempFirstName = [[NSMutableString alloc]init ];
}
if([elementName isEqualToString:#"lastname"]){
tempLastName = [[NSMutableString alloc]init ];
}
if([elementName isEqualToString:#"jobtitle"]){
tempJobTitle = [[NSMutableString alloc]init ];
}
if([elementName isEqualToString:#"departmentid"]){
tempDeptId = [[NSMutableString alloc]init ];
}
if([elementName isEqualToString:#"parentid"]){
tempParentId = [[NSMutableString alloc]init];
}
}
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string{
if([currentElement isEqualToString:#"id"]){
[tempId appendString:string];
}
if([currentElement isEqualToString:#"firstname"]){
[tempFirstName appendString:string];
}
if([currentElement isEqualToString:#"lastname"]){
[tempLastName appendString:string];
}
if([currentElement isEqualToString:#"jobtitle"]){
[tempJobTitle appendString:string];
}
if([currentElement isEqualToString:#"departmentid"]){
[tempDeptId appendString:string];
}
if([currentElement isEqualToString:#"parentid"]){
[tempParentId appendString:string];
}
}
-(void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"employee"]){
[employee setIdNumber:[tempId intValue]];
[tempId release];
[employee setFirstName:tempFirstName];
[tempFirstName release];
[employee setLastName:tempLastName];
[tempLastName release];
[employee setJobTitle:tempJobTitle];
[tempJobTitle release];
[employee setDepartmentIdNumber:[tempDeptId intValue]];
[tempDeptId release];
[employee setParentIdNumber:[tempParentId intValue]];
[tempParentId release]; //IF I REMOVE THIS LINE, THE PROGRAM DOES NOT CRASH
[employees addObject:employee];
[employee release];
}
}
#end
I am experiencing a very strange problem with it. When I am to call the start method implemented in IdParser, it parses everything but when it gets to the last node of the XML (parentid), something strange happens.
The program quits and I receive the following error message:
malloc: * error for object 0x4b33360: incorrect checksum for freed object - object was probably modified after being freed.
* set a breakpoint in malloc_error_break to debug
Current language: auto; currently objective-c
No memory available to program now: unsafe to call malloc
Strangely, when I remove the [tempParentId release]; line, the program runs fine. I have tried rearranging the elements in the XML and the same thing happens again: the program crashes at the last element. It does no make sense to me what is causing the problem as I am new to Objective-C and iOS so I am asking for help. I guess there is a memory problem somewhere because the program runs fine after I remove the line that I mentioned above.
Thanks for any help.
Petar
EDIT:
As I said I am new to Obj-C and I don't understand much about memory management and all the things connected with it so I am using this example to learn and expand my knowledge about it. That said, can you please try to explain what exactly is causing the error described before suggesting how to fix it.
EDIT2:
Sometimes when I run the code, instead of the error message which I described above, the program freezes and in the console I see just:
Current language: auto; currently objective-c
(gdb)
This may be a clue as to what the problem is because I am experiencing random behaviour.
EDIT3:
Employee class:
#import <Foundation/Foundation.h>
#interface Employee : NSObject {
int idNumber;
NSString *firstName;
NSString *lastName;
int departmentIdNumber;
NSString *jobTitle;
int parentIdNumber;
}
-(id)initWithIdNumber:(int)idValue
firstName:(NSString *)firstNameValue
lastName:(NSString *)lastNameValue
departmentIdNumber:(int)departmentIdNumberValue
jobTitle:(NSString *)jobTitleValue
parentIdNumber:(int)parentIdNumberValue;
#property(nonatomic) int idNumber;
#property(nonatomic, retain) NSString *firstName;
#property(nonatomic, retain) NSString *lastName;
#property(nonatomic, retain) NSString *jobTitle;
#property(nonatomic) int departmentIdNumber;
#property(nonatomic) int parentIdNumber;
#end
#import "Employee.h"
#implementation Employee
#synthesize idNumber, firstName, lastName, departmentIdNumber, jobTitle, parentIdNumber;
-(id)initWithIdNumber:(int)idValue
firstName:(NSString *)firstNameValue
lastName:(NSString *)lastNameValue
departmentIdNumber:(int)departmentIdNumberValue
jobTitle:(NSString *)jobTitleValue
parentIdNumber:(int)parentIdNumberValue{
self = [super init];
if(self){
[self setIdNumber:idValue];
[self setFirstName:firstNameValue];
[self setLastName:lastNameValue];
[self setDepartmentIdNumber:departmentIdNumberValue];
[self setJobTitle:jobTitleValue];
[self setParentIdNumber:parentIdNumberValue];
}
return self;
}
-(NSString *) description{
NSString *desc = [[NSString alloc]initWithFormat:#"ID: %d, firstname: %#, lastname: %#, departmentID: %d, jobtitle: %#, parentID: %d", idNumber, firstName, lastName, departmentIdNumber, jobTitle, parentIdNumber];
return desc;
}
#end

I recommend you enable ARC, which will instruct the compiler to write all memory management code for you. As far as I can tell, it does a better job than most experienced Objective-C developers, and certainly better than new ones.
Everything you need to know about ARC is here: http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html
You can enable it for an existing project with Edit -> Refactor -> Convert to Objective-C ARC…. Which will modify the code in your project to be ARC compatible (so make sure you create a backup or commit to source control before doing this!).
Beware ARC code will not run on very old versions of iOS. Shouldn't be an issue though.

You are breaking the first rule of ObjC memory management: don't access your ivars directly. Use accessors everywhere but dealloc and init. This will take care of most memory management problems (and solves several other problems to boot). You have a lot of tricky memory management in the above code that would all go away if you used accessors.
#Abhi Beckert's comment about ARC is good, and if you can use ARC you should. IMO, it is the best addition to ObjC many years, and everyone should use it. But even with ARC, use accessors.

Related

iOS - parse xml DATASET

This is my XML data that I need to parse
<GetMessagesResult>
<NewDataSet xmlns="">
<Table>
<Date>21:52:59</Date>
<Message>ABC</Message>
<GroupName>ALL</GroupName>
</Table>
<Table>
<Date>11:23:27</Date>
<Message>DEF</Message>
<GroupName>ALL</GroupName>
</Table>
</NewDataSet>
</GetMessagesResult>
This is my SCMessages.h file
#import <Foundation/Foundation.h>
#interface SCMessages : NSObject
{
NSDate *Date;
NSString *Message;
NSString *GroupName;
}
#property (nonatomic, retain) NSDate *Date;
#property (nonatomic, retain) NSString *Message;
#property (nonatomic, retain) NSString *GroupName;
and this is my SCMessages.m file
#import "SCMessages.h"
#implementation SCMessages
#synthesize Date;
#synthesize Message,GroupName;
- (void)dealloc
{
[super dealloc];
[Date release];
[Message release];
[GroupName release];
}
#end
I used below code to parse the data using NSXMLParser delegate methods
#pragma mark - NSXMLParser Delegate
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *)qName
attributes: (NSDictionary *)attributeDict
{
if( [elementName isEqualToString:#"GetMessagesResult"])
{
if(!soapResults)
soapResults = [[NSMutableString alloc] init];
recordResults = TRUE;
}
if([elementName isEqualToString:#"NewDataSet"]) {
//Initialize the array.
messages = [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:#"Table"]) {
//Initialize the message.
aMessage = [[SCMessages alloc] init];
}
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if( recordResults )
[soapResults appendString: string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"Table"]) {
[messages addObject:aMessage];
NSLog(#"MESSAGES COUNT: %d",messages.count);
NSLog(#"MESSAGE: %#",aMessage);
[aMessage release];
aMessage = nil;
}
// as this is the last element
if( [elementName isEqualToString:#"NewDataSet"])
{
recordResults = FALSE;
}
}
PROBLEM I am not getting the desired message object with Date, Meesage & GroupName.
I put NSLog to print them but I always get null value. The weird thing is message array gets allocated memory & also elements gets added to array as I can see message array count in NSLog but the array element has data as null value.
I am parsing the XML data received in SOAP response in NSURLConnection delegate method connectionDidFinishLoading
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
NSLog(#"theXML: \n%#",theXML);
You need to store your values in aMessage. You have added particular object to array but you forgot to store data in that object of SCMessages class.You can also check while debug that you are getting value in aMessage object.
You need to store value like this in this didEndElement method.
[aMessage setValue: soapResults forKey:elementName];
I think this will help you.
Your didEnd method is missing code to store the accumulated string into a property. You need an ivar for the current SCMessage object.
Append text content to a mutable string ivar. Then on closing an element you have to decide which property to set on SCMessage. You probably have to parse the date into an NSDate.

iOS: Parsing a xml from HTTP using NSXMLParser for

It's first time using NSXMLParser and wondering if you give me some direction of parsing the returned xml from an http request that looks like:
<?xml version="1.0" ?>
<theresponse>
<status>OK</status>
<pricing currency="USD" symbol="$">
<price class="items">24.00</price>
<price class="shipping">6.00</price>
<price class="tax">1.57</price>
</pricing>
</theresponse>
I know the basic of parsing delegate methods, I just want to know what the code would look like in didEndElement/foundCharacters/didStartElement for retreiving above items(currency/items/shipping/tax)? any help greatly appreciated.
This is a little more tricky than some standard NSXMLParser code; Because essentially when you are looking for the "shipping" you want "6.00" but those two pieces of data are returned to you in different delegate methods, which is normal. But usually the element would be named "shipping" so in parser:didEndElement:namespaceURI:qualifiedName: you would automatically have the element name as it was passed into the method.
The solution would seem simple, have a _currentAttributes ivar and in parser:didStartElement:namespaceURI:qualifiedName:attributes: do something like _currentAttributes = attributeDict; and then handle this in the didEndElement: method. However this style would easily break, even on this moderately simple XML.
My way of handling this would be to store the attributes dictionary passed into the didStartElement: and set it in a dictionary as the object for the key of the element name. Combining this style with the standard use of an NSMutableString as a characterBuffer of sorts allows you to put all of your logic into the didEndElement: method.
Side note: I am also quite fond of having my NSXMLParserDelegate classes be NSXMLParser subclasses, as this one is. However the delegate methods would be identical if it were not.
ItemParser.h
#import <Foundation/Foundation.h>
#interface ItemParser : NSXMLParser <NSXMLParserDelegate>
#property (readonly) NSDictionary *itemData;
#end
ItemParser.m
#import "ItemParser.h"
#implementation ItemParser {
NSMutableDictionary *_itemData;
NSMutableDictionary *_attributesByElement;
NSMutableString *_elementString;
}
-(NSDictionary *)itemData{
return [_itemData copy];
}
-(void)parserDidStartDocument:(NSXMLParser *)parser{
_itemData = [[NSMutableDictionary alloc] init];
_attributesByElement = [[NSMutableDictionary alloc] init];
_elementString = [[NSMutableString alloc] init];
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
// Save the attributes for later.
if (attributeDict) [_attributesByElement setObject:attributeDict forKey:elementName];
// Make sure the elementString is blank and ready to find characters
[_elementString setString:#""];
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
// Save foundCharacters for later
[_elementString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"status"]){
// Element status only contains a string i.e. "OK"
// Simply set a copy of the element value string in the itemData dictionary
[_itemData setObject:[_elementString copy] forKey:elementName];
} else if ([elementName isEqualToString:#"pricing"]) {
// Pricing has an interesting attributes dictionary
// So copy the entries to the item data
NSDictionary *attributes = [_attributesByElement objectForKey:#"pricing"];
[_itemData addEntriesFromDictionary:attributes];
} else if ([elementName isEqualToString:#"price"]) {
// The element price occurs multiple times.
// The meaningful designation occurs in the "class" attribute.
NSString *class = [[_attributesByElement objectForKey:elementName] objectForKey:#"class"];
if (class) [_itemData setObject:[_elementString copy] forKey:class];
}
[_attributesByElement removeObjectForKey:elementName];
[_elementString setString:#""];
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
_attributesByElement = nil;
_elementString = nil;
}
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(#"%# with error %#",NSStringFromSelector(_cmd),parseError.localizedDescription);
}
-(BOOL)parse{
self.delegate = self;
return [super parse];
}
#end
And so to test I stored the XML you posted above into a file named "ItemXML.xml". And tested it using this code:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"ItemXML" withExtension:#"xml"];
ItemParser *parser = [[ItemParser alloc] initWithContentsOfURL:url];
[parser parse];
NSLog(#"%#",parser.itemData);
The result I got was:
{
currency = USD;
items = "24.00";
shipping = "6.00";
status = OK;
symbol = "$";
tax = "1.57";
}

how to retain values in the following method?

#interface SignDocumentController : UIViewController<NSXMLParserDelegate> {
NSMutableString *signFaxString;
NSString * messageId;
NSMutableData *xmlData;
NSURLConnection *connectionInprogress;
NSURLConnection *connectionInprogress2;
NSString * annotationKey;
NSString *firstName;
NSString *lastName;
NSString *date;
NSString *signature;
IBOutlet UIImageView *image;
}
#property(nonatomic,retain)UIImageView * image;
#end
-(void)parser:(NSXMLParser *)parser
didStartElement:(NSString *) elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:#"SignatureInfo"]) {
signFaxString = [[NSMutableString alloc]init];
firstName = [attributeDict objectForKey:#"FirstName"];
lastName = [attributeDict objectForKey:#"LastName"];
date = [attributeDict objectForKey:#"Date"];
signature = [attributeDict objectForKey:#"Signature"];
}
if ([elementName isEqual:#"AddAnnotationResult"]) {
signFaxString = [[NSMutableString alloc]init];
}
}
the values for firstName, lastName, date, signature do not stay and I get an error when I try accessing firstName, lastName ETC in a different method:
[CFString respondsToSelector:]: message sent to deallocated instance 0x4ec63b0
I have tried using :
firstName = [NSString stringWithString attributeDict objectForKey:#"FirstName"];
but that does not work either. I know this is a silly question but I could use some help.
Thanks
you could also declare the firstName and others as property and retain . As below
#property(nonatomic,retain)NSString* firstName;
#property(nonatomic,retain)NSString* lastName;
#property(nonatomic,retain)NSString* date;
#property(nonatomic,retain)NSString* signature;
And in .m class.
#synthesize firstName,date,lastName,signature;
and release them in dealloc function.
Use with self all your property variable in you class.
self.firstName = [NSString stringWithString:attributeDict objectForKey:#"FirstName"];
EDITED:
Also consider #bbum comment ..
To retain it, just send a retain message to the object.
firstName = [[attributeDict objectForKey:#"FirstName"] retain];
release it later.

IPhone Development - Generate XML File

Does anybody here know how to generate a XML file or string in the iPhone?
Try the open source XML stream writer for iOS:
Written in Objective-C, a single .h. and .m file
One #protocol for namespace support and one for without
Example:
// allocate serializer
XMLWriter* xmlWriter = [[XMLWriter alloc]init];
// start writing XML elements
[xmlWriter writeStartElement:#"Root"];
[xmlWriter writeCharacters:#"Text content for root element"];
[xmlWriter writeEndElement];
// get the resulting XML string
NSString* xml = [xmlWriter toString];
This produces the following XML string:
<Root>Text content for root element</Root>
<?xml version="1.0" encoding="UTF-8"?> <parent>
<child name="James"> <age>10</age> <sex>male</sex>
</child> </parent>
</xml>
like i use this xml
write in .h file........
#import <UIKit/UIKit.h>
#interface RootViewController : UIViewController <NSXMLParserDelegate> {
#public
}
NSXMLParser *myXMLParser;
#property (nonatomic, retain) NSXMLParser *myXMLParser;
#end
write in .m file
- (void)viewDidLoad { [super viewDidLoad];
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *xmlFilePath = [mainBundle pathForResource:#"MyXML" ofType:#"xml"];
if ([xmlFilePath length] == 0){ /* The file could not be found in the resources folder.
}
return;
/* Let's see if the XML file exists... */
NSFileManager *fileManager = [[NSFileManager alloc] init];
if ([fileManager fileExistsAtPath:xmlFilePath] == NO){
/* The XML file doesn't exist, we double checked this just to make sure we don't leave any stones unturned. You can now throw an error/exception here or do other appropriate processing */
NSLog(#"The file doesn't exist.");
[fileManager release];
return;
[fileManager release];
/* Load the contents of the XML file into an NSData */ NSData *xmlData = [NSData dataWithContentsOfFile:xmlFilePath];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmlData];
self.myXMLParser = xmlParser; [xmlParser release];
[self.myXMLParser setDelegate:self];
if ([self.myXMLParser parse] == YES){
/* Successfully started to parse */ } else {
}
/* Failed to parse. Do something with this error */
NSError *parsingError = [self.myXMLParser parserError];
NSLog(#"%#", parsingError);
- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
NSLog(#"Element Name = %#", elementName);
NSLog(#"Number of attributes = %lu",(unsigned long)[attributeDict count]);
if ([attributeDict count] > 0)
{
NSLog(#"Attributes dictionary = %#", attributeDict);
NSLog(#"========================");
}
}

Core Data with NSXMLParser on iPhone saving object incorrectly

I'm new to Objective-C, XCode and iPhone development in general and I'm having some issues with Core Data and NSXMLParser.
Having followed Apples' tutorials SeismicXML (for NSXMLParser) and the Core Data on iPhone tutorial I've ran into an issue when assigning values to my Managed Object Models' entities properties.
To explain the situation my code only varies from the SeismicXML example by using CoreData to assign the currentParsedCharacterData to my managed objects rather than the standard NSObject which the SeismicXML project uses.
Below is the description output from my managed object.
county = "-53.25354768,4.256547";
friendly = "-53.25354768,4.256547";
image = nil;
latitude = -53.253547684;
link = "-53.25354768,4.256547";
longitude = nil;
name = "-53.25354768,4.256547";
postcode = "-53.25354768,4.256547";
shopDescription = nil;
shopID = 0;
tag = "-53.25354768,4.256547";
tags = (
);
telephone = "-53.25354768,4.256547";
town = "-53.25354768,4.256547";
What appears to be happening is that all of the attributes/properties are assigned the value of the last node in my XML feed; which happens to be longitude, latitude. Yet when logging parsed character data at the time of the property assignment it is the expected (and correct) value but when outputting the description of this object all string values are wrong and number values/otherwise are simply 0 or nil.
Any suggestions would be extremely appreciated. If need be I can knock up a smaller project which shows this behaviour with the same XML feed that I am using.
EDIT:
Here is an abbreviated example of what I am doing to get information into the managed object which results in the same error.
For convenience sake I have a zip of the project http://willb.ro/CoreDataProblemExample.zip
Debug Output 2009-11-16 14:31:20.357
ShittyExample[4360:4d07] Company
Description:
(entity: Company; id: 0x3f6e9e0
; data: {
companyDescription = "Top Shop are a leading brandname in the retail
sec";
companyID = 66136112;
name = "Top Shop are a leading brandname in the retail sec"; })
//XML
<channel>
<company id="1">
<name>Top Shop</name>
<description>Top Shop are a leading brandname in the retail sector.</description>
</company>
</channel>
// FeedImporter.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class RootViewController, Company;
#interface FeedImporter : NSObject {
NSManagedObjectContext *managedObjectContext;
RootViewController *rootViewController;
NSMutableArray *companyList;
// for downloading the xml data
NSURLConnection *companyFeedConnection;
NSMutableData *companyData;
// these variables are used during parsing
Company *currentCompanyObject;
NSMutableArray *currentParseBatch;
NSUInteger parsedCompaniesCounter;
NSMutableString *currentParsedCharacterData;
BOOL accumulatingParsedCharacterData;
BOOL didAbortParsing;
}
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) RootViewController *rootViewController;
#property (nonatomic, retain) NSMutableArray *companyList;
#property (nonatomic, retain) NSURLConnection *companyFeedConnection;
#property (nonatomic, retain) NSMutableData *companyData;
#property (nonatomic, retain) Company *currentCompanyObject;
#property (nonatomic, retain) NSMutableString *currentParsedCharacterData;
#property (nonatomic, retain) NSMutableArray *currentParseBatch;
- (void)parseFeed;
- (void)addCompaniesToList:(NSArray *)companies;
- (void)handleError:(NSError *)error;
#end
// FeedImporter.m
#import "FeedImporter.h"
#import "RootViewController.h"
#import <CFNetwork/CFNetwork.h>
#import "Company.h"
#implementation FeedImporter
#synthesize managedObjectContext;
#synthesize rootViewController;
#synthesize companyList;
#synthesize companyFeedConnection;
#synthesize companyData;
#synthesize currentCompanyObject;
#synthesize currentParseBatch;
#synthesize currentParsedCharacterData;
- (void)dealloc {
[super dealloc];
[managedObjectContext release];
[rootViewController release];
[companyList release];
[companyFeedConnection release];
[companyData release];
[currentCompanyObject release];
[currentParseBatch release];
[currentParsedCharacterData release];
}
- (id)init {
if(self = [super init]) {
// Custom loading logic goes here..
}
return self;
}
- (void)parseFeed {
static NSString *feedURLString = #"http://willb.ro/companies.xml";
NSURLRequest *companyURLRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]];
self.companyFeedConnection = [[[NSURLConnection alloc] initWithRequest:companyURLRequest delegate:self] autorelease];
NSAssert(self.companyFeedConnection != nil, #"Failure to create URL connection.");
// Start the status bar network activity indicator. We'll turn it off when the connection finishes or experiences an error.
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
#pragma mark NSURLConnection delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.companyData = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[companyData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if ([error code] == kCFURLErrorNotConnectedToInternet) {
// if we can identify the error, we can present a more precise message to the user.
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(#"No Connection Error", #"Error message displayed when not connected to the Internet.") forKey:NSLocalizedDescriptionKey];
NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:userInfo];
[self handleError:noConnectionError];
} else {
// otherwise handle the error generically
[self handleError:error];
}
self.companyFeedConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.companyFeedConnection = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[NSThread detachNewThreadSelector:#selector(parseCompanyData:) toTarget:self withObject:companyData];
self.companyData = nil;
}
- (void)parseCompanyData:(NSData *)data {
// You must create a autorelease pool for all secondary threads.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
self.currentParseBatch = [NSMutableArray array];
self.currentParsedCharacterData = [NSMutableString string];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
if ([self.currentParseBatch count] > 0) {
[self performSelectorOnMainThread:#selector(addCompaniesToList:) withObject:self.currentParseBatch waitUntilDone:NO];
}
self.currentParseBatch = nil;
self.currentCompanyObject = nil;
self.currentParsedCharacterData = nil;
// Save to our MOC...
NSError *saveError;
if(![self.managedObjectContext save:&saveError]) {
// Handle MOM save error
NSLog(#"error while saving shop to managed object model");
NSError* error;
if(![[self managedObjectContext] save:&error]) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
}
else
{
NSLog(#"MOC saved sucessfully");
}
[parser release];
[pool release];
}
#pragma mark Parser constants
// Limit the number of parsed companies to 50.
static const const NSUInteger kMaximumNumberOfCompaniesToParse = 50;
static NSUInteger const kSizeOfCompanyBatch = 10;
static NSString * const kChannelElementName = #"channel";
static NSString * const kCompanyElementName = #"company";
static NSString * const kCompanyNameElementName = #"name";
static NSString * const kCompanyDescriptionElementName = #"description";
- (void)addCompaniesToList:(NSArray *)companies {
[self.companyList addObjectsFromArray:companies];
// The table needs to be reloaded to reflect the new content of the list.
[rootViewController.tableView reloadData];
}
#pragma mark NSXMLParser delegate methods
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if (parsedCompaniesCounter >= kMaximumNumberOfCompaniesToParse) {
didAbortParsing = YES;
[parser abortParsing];
}
if ([elementName isEqualToString:kCompanyElementName]) {
Company *company = (Company *)[NSEntityDescription insertNewObjectForEntityForName:#"Company" inManagedObjectContext:self.managedObjectContext];
self.currentCompanyObject = company;
[company release];
int companyIDInt = (int)[attributeDict valueForKey:#"id"];
NSNumber *companyID = [NSNumber numberWithInt:companyIDInt];
[self.currentCompanyObject setCompanyID:companyID];
}
else if ([elementName isEqualToString:kCompanyElementName] || [elementName isEqualToString:kCompanyNameElementName] || [elementName isEqualToString:kCompanyDescriptionElementName]) {
accumulatingParsedCharacterData = YES;
[currentParsedCharacterData setString:#""];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:kCompanyElementName]) {
//NSLog(#"currentEarthquakeObject: %#", currentEarthquakeObject);
[self.currentParseBatch addObject:self.currentCompanyObject];
parsedCompaniesCounter++;
if (parsedCompaniesCounter % kSizeOfCompanyBatch == 0) {
[self performSelectorOnMainThread:#selector(addCompaniesToList:) withObject:self.currentParseBatch waitUntilDone:NO];
self.currentParseBatch = [NSMutableArray array];
}
//NSLog(#"Reached end of company. Follows is a description of our company object: %#", [self.currentCompanyObject description]);
NSLog(#"Company Description: %#", [self.currentCompanyObject description]);
}
else if ([elementName isEqualToString:kCompanyNameElementName]) {
// Company Name
[self.currentCompanyObject setName:self.currentParsedCharacterData];
//NSLog(#"%#",self.currentParsedCharacterData);
}
else if ([elementName isEqualToString:kCompanyDescriptionElementName]) {
// Company Description
[self.currentCompanyObject setCompanyDescription:self.currentParsedCharacterData];
//NSLog(#"%#",self.currentParsedCharacterData);
}
accumulatingParsedCharacterData = NO;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (accumulatingParsedCharacterData) {
[self.currentParsedCharacterData appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
if (didAbortParsing == NO) {
[self performSelectorOnMainThread:#selector(handleError:) withObject:parseError waitUntilDone:NO];
}
}
- (void)handleError:(NSError *)error {
NSString *errorMessage = [error localizedDescription];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error Title", #"Title for alert displayed when download or parse error occurs.") message:errorMessage delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
#end
It seems you are assigning where you ought to be copying. I'd have to see more of your code to be sure, but I'm almost certain a copy somewhere would solve your issues.
As stated above, i had the exact same problem. But would now recommend to bypass the useless NSXMLParser (if using it WITH core data!!!) and using GDataXML instead. GDataXML is modelled on the NSXMLDocument class (which is a more elegant and workable solution to use with core data, only that you can't use NSXMLDocument with iOS).
See here for an excellent tutorial:
http://www.raywenderlich.com/725/how-to-read-and-write-xml-documents-with-gdataxml
:)