Petar
Petar

Reputation: 2283

NSXMLParser does not parse last node (possible memory issue)

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

Upvotes: 1

Views: 333

Answers (2)

Rob Napier
Rob Napier

Reputation: 299275

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.

Upvotes: 2

Abhi Beckert
Abhi Beckert

Reputation: 33349

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.

Upvotes: 2

Related Questions