【问题标题】:NSXMLParser does not parse last node (possible memory issue)NSXMLParser 不解析最后一个节点(可能的内存问题)
【发布时间】:2011-12-06 19:22:06
【问题描述】:

我有以下 .xml 文件:

<?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>
...

以下头文件:

#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

以及以下实现:

#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

我遇到了一个非常奇怪的问题。当我要调用在 IdParser 中实现的 start 方法时,它会解析所有内容,但是当它到达 XML 的最后一个节点(parentid)时,会发生一些奇怪的事情。

程序退出,我收到以下错误消息:

malloc: * 对象 0x4b33360 错误:已释放对象的校验和不正确 - 对象可能在被释放后被修改。 * 在 malloc_error_break 中设置断点进行调试 当前语言:自动;目前客观-c 现在没有可用于编程的内存:调用 malloc 不安全

奇怪的是,当我删除 [tempParentId release]; 行时,程序运行良好。我尝试重新排列 XML 中的元素,但同样的事情再次发生:程序在最后一个元素处崩溃。由于我是 Objective-C 和 iOS 的新手,所以导致问题的原因对我来说毫无意义,所以我正在寻求帮助。我猜某处存在内存问题,因为在我删除上面提到的行后程序运行良好。

感谢您的帮助。

佩塔

编辑:

正如我所说,我是 Obj-C 的新手,我不太了解内存管理以及与之相关的所有内容,因此我使用这个示例来学习和扩展我的知识。也就是说,您能否在建议如何修复它之前尝试解释究竟是什么导致了所描述的错误。

EDIT2:

有时当我运行代码时,程序会冻结,而不是上面描述的错误消息,并且在控制台中我只看到:

当前语言:自动;目前客观-c (gdb)

这可能是问题所在的线索,因为我遇到了随机行为。

EDIT3:

员工类:

#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

【问题讨论】:

  • 这就是你的全部代码吗?你确定你没有在其他地方对tempParentId 做任何事情吗?
  • 这是所有的代码。 tempParentId 在头文件中定义并在实现文件中访问。我已经完整地复制了它们。谢谢
  • 有点长镜头,但在发布后立即尝试将tempParentId 设置为nil。我不明白为什么在您的代码中需要这样做,但它被认为是一种很好的做法。
  • @AbhiBeckert:将 tempParentId 设置为 nil 实际上解决了问题:) 但是,我不明白为什么。为什么我不应该将其他变量也设置为 nil ?你能解释一下发生了什么吗?非常感谢
  • 一般来说,当你完成一个变量时,你应该将它设置为nil 以防止任何东西试图使用它。但是,如果将其设置为nil 解决了问题,那么您的代码中一定有一些严重的错误(我在您发布的内容中没有看到任何错误),您应该尝试找到它。该错误可能是也可能不是内存管理问题... Objective-C 是一种低级语言,您正在处理指针。因此,该错误可能在您的应用中几乎在任何地方出现。这就是我打开 ARC 的原因,所以我从来没有这样的头痛。您应该将其他设置为nil

标签: objective-c ios nsxmlparser


【解决方案1】:

我建议您启用 ARC,它将指示编译器为您编写所有内存管理代码。据我所知,它比大多数有经验的 Objective-C 开发人员做得更好,当然也比新开发人员做得更好。

您需要了解的关于 ARC 的一切都在这里:http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html

您可以使用Edit -&gt; Refactor -&gt; Convert to Objective-C ARC… 为现有项目启用它。这会将您项目中的代码修改为与 ARC 兼容(因此请确保在执行此操作之前创建备份或提交到源代码管理!)。

注意 ARC 代码无法在非常旧的 iOS 版本上运行。不过应该不是问题。

【讨论】:

  • 感谢您的建议,但这并不能解决我的问题。如果我想使用 ARC,我永远不会问这个问题。我想在没有 ARC 的情况下编写我的程序,因此寻求帮助。
  • 提供它是因为我知道它几乎肯定会解决您的问题。除了向后兼容之外,关闭 ARC 的理由并不多。
  • 手动内存管理:ARC :: Washboard:洗衣机
  • 不管怎样,如果 XML 包含任何无效结构,您的代码中肯定存在一些内存泄漏/错误。开启 ARC 至少可以修复漏洞。
  • 作为一个长期的 ObjC 开发人员,他对人们真正学习 ObjC 而不仅仅是“破解它”感到疯狂,即使我说:使用 ARC。在 iOS 5+ 或 10.7+ 上工作的新开发人员几乎没有理由学习手动内存管理,除非他们已经开发了一段时间并且需要与 Core Foundation 交互。内存管理曾经是一项基本的“你必须在第一天就知道”的技能。现在,像 ARC 鼓励的那样在对象图中思考要好得多。您将编写更简洁、更稳定、运行更快的代码。顺便说一句,它更容易编写。
【解决方案2】:

您违反了 ObjC 内存管理的第一条规则:不要直接访问您的 ivars。除了deallocinit 之外的任何地方都可以使用访问器。这将解决大多数内存管理问题(并解决其他几个启动问题)。在上面的代码中,你有很多棘手的内存管理,如果你使用访问器,这些都会消失。

@Abhi Beckert 关于 ARC 的评论很好,如果你可以使用 ARC,你应该这样做。 IMO,它是多年来对 ObjC 最好的补充,每个人都应该使用它。但即使使用 ARC,也要使用访问器。

【讨论】:

  • 直接访问 ivars 的另一个原因是性能,这可能是 XML 解析器中的一个有效点。但是在直接访问它们之前,他的代码还需要许多其他优化。
  • 可以,而且我已经为一些非常高性能的代码片段做了它(用 cmets 解释了为什么它在这种情况下是合适的),并且偶尔需要与 C 和 C++ 代码交互您需要通过引用传递实际地址的地方。但正如您所说,这是您为非常特殊的代码所做的事情,而不是正常行为。
  • @RobNapier:如果我要在实现文件中将实例变量作为全局变量,我还会打破内存管理规则吗?如果我要将变量作为全局变量,我的程序是否应该工作?谢谢
  • 实例变量的访问器方法不需要公开。大多数打算供第三方使用的类都有一些私有访问器方法,尽管我通常不会为我的代码费心。
  • 你为什么要让这些全球化?你的意思是公开的吗?正如@AbhiBeckert 所说,您不必公开属性。您可以在 .m 文件内的私有接口扩展中声明它们。
猜你喜欢
  • 1970-01-01
  • 2016-05-01
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 2010-11-14
相关资源
最近更新 更多