3

I have a method call in class A:

GDataXMLElement *infoElement = [self getElementFromFilePath:filePath];
NSString *testStringA = [infoElement attributeForName:@"someAttribute"].stringValue;

and the method implementation in class B:

-(GDataXMLElement*)getElementFromFilePath:(NSString*)filePath {
    NSData *xmlData = [NSData dataWithContentsOfFile:filePath];
    GDataXMLDocument *infoXMLDoc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:nil];
    NSArray *infoArray = [infoXMLDoc.rootElement elementsForName:@"test"];
    GDataXMLElement *returnElement = (GDataXMLElement*)infoArray[0];
    NSString *testStringB = [returnElement attributeForName:@"someAttribute"].stringValue;
    return returnElement;
}

The returnElement at the end of the method in class B is perfectly initialized, and testStringB string contains the correct value. But in Class A, the contents of InfoElement are gone, and testStringA is nil.

I suspect that ARC is releasing GDataXMLDocument too early, and was able to stop this behaviour by tying the document to a property in class B:

@property (nonatomic,strong) GDataXMLDocument *infoXMLDoc;

But I am a little unsatisfied with this solution. I'll never use that property again, I just need the element to parse it one time only. If it is possible to stop the release with a property, is there also a way to do this within the method? I tried the __strong qualifier like this:

GDataXMLDocument __strong *infoXMLDoc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];

but that didn't help. So my questions, assuming ARC is indeed responsible:

1. Is is possible to tell ARC within a method to not release an object?

2. What did I not understand about the behaviour here? I am using ARC for some time now, this is the first time that I am stuck.

marimba
  • 3,116
  • 5
  • 26
  • 29
  • When you say that "the contents of `InfoElement` are gone", do you mean that it has a `GDataXMLElement` assigned to it, but that object doesn't contain what you expect? Or do you mean that it is `nil`? – Jim Feb 22 '13 at 15:17
  • 1
    That code won't compile; there is no `infoElement` in `getElementFromFilePath()`. – trojanfoe Feb 22 '13 at 15:19
  • 1
    Have you followed [the instructions for adding GData to a project](http://code.google.com/p/gdata-objectivec-client/wiki/BuildingTheLibrary), including disabling ARC for the library files? – Jim Feb 22 '13 at 15:22
  • trojanfoe, edited the code. Jim, the latter, GDataXMLElement is assigned, but xml content is empty. Yes, ARC is disabled for GDataXMLNode, wouldn't compile otherwise. – marimba Feb 22 '13 at 15:29

2 Answers2

2

The GDataXMLNode.h header says:

it is up to the code that created a document to retain it for as long as any references rely on nodes inside that document tree.

The node you return from getElementFromFilePath depends on the parent (the GDataXMLDocument), but that is going to be released by ARC. You must retain the GDataXMLDocument somewhere for as long as you reference nodes and elements inside it. These are the semantics of the GDataXML* classes and you must follow them.

Mike Weller
  • 45,401
  • 15
  • 131
  • 151
  • Thanks! This is what I suspected, but what is the nicest way to do that? Adding the @property looks a little ugly IMHO... – marimba Feb 22 '13 at 15:27
  • You could return the document instead of the element itself, which would retain it for the time being, but that kind of defeats the purpose of your method. Otherwise I'm not quite sure other than your method of doing it. – Philippe Sabourin Feb 22 '13 at 15:36
  • Generally you would parse your GDataXMLDocument and save it into a property. Then you'd have further methods which operate on that document and return the sub-elements that you are interested in. So change your internal API a little to make it clear that the document is the top-level root element which you need to keep around. – Mike Weller Feb 22 '13 at 15:38
  • Hm. I marked your answer as correct, but still...Class B has a long lifetime and is initializing itself from the XML. Afterwards, the XML is not needed anymore. Class A is one of many subclasses of B, all of them initialize themselves the same way, that's why I tried to put that logic into the base class. Now I will carry the GDataXMLDocument as long as those classes live. Not really satisfying. But thank you for your help, anyway! – marimba Feb 22 '13 at 16:25
  • You will have to pull out the data you need and put it into another object. Each XML node/element has a reference to the parent, and that has a reference to its parent, all the way up the chain. This is just how libxml and GDataXML store their representation. If you want to throw that away you need to create your own domain objects to hold the data. – Mike Weller Feb 22 '13 at 16:37
  • Yes, you're right. I think I will keep the document instead of the element, as you suggested. – marimba Feb 22 '13 at 16:57
0

I can't compile so this's just an educated guessing, but I suspect the problem is that you return a pointer to an object that is allocated and released inside the method:

GDataXMLElement *returnElement = (GDataXMLElement*)infoArray[0];

As you see you don't alloc returnElement, so ARC have no way to understand that you need it. It simply release infoArray when you exit from the method. If you copy the value (something like [(GDataXMLElement*)infoArray[0] copy] ) it should works.