PDA

View Full Version : Cocoa Objective-C Problem: Passing NSMutableArray Problem




Kelmon
Jul 31, 2006, 03:44 PM
Well, I have no idea what is going on here. I have written an application (my first, incidentally, so this is no doubt a newbie error) that keeps falling over when trying to access an instance of NSMutableArray. The synopsis of the problem is as follows:

1. The application consists of an AppController object that is a subclass of NSObject. When the application is started the init method gets called an as part of the setup an instance of NSArray is created that is populated with a shedload of NSString objects (it's basically the data from a CSV file). The application displays a window and awaits the user to enter an integer and press a button. The instance of NSArray is sent a retain method when it is created using an accessor method.

2. When data is entered and the button pushed an IBAction method creates an instance of NSMutableArray using a NSRange within the NSArray created earlier. This object is passed as an argument to the initialisation method of a custom class.

3. When the custom class is setup it calls its superclass (another custom class) and the superclass adds the instance of NSMutableArray using an accessor method in which it is sent a retain message.

4. Following setup of the superclass and the child class that was instantiated explicitly, the child class attempts to access the contents of the NSMutableArray using an accessor method to count the number of objects and BANG!

The output from the debugger appears to suggest that the NSMutableArray object has been released somehow and I can only assume that the Autorelease has something to do with it, although I can't see how. When the NSMutableArray is added to the custom class I put a check in place to verify the retaincount and it's 2. I do not release any of my objects until it comes time for deallocation of the complete object so how is this object getting released?

What mystifies me even more here is that I am able pass to the custom class an instance of NSDictionary that, again, is handled by the superclass but yet this causes me no problems. Is there a scope issue whereby a child class cannot access an instance variable of its parent class?

This is really doing my head in so any assistance is greatly appreciated.



gekko513
Jul 31, 2006, 03:55 PM
How do you create and fill the NSMutableArray instance in point 2?

A suggestion: Debug the application, put a breakpoint in the IBAction method that you mention in point 2, then step forward and see if everything goes as you think it should, that the NSMutableArray follows along to the initialiser and the super class' initialiser as well and that the retain count is as expected at all points.

You've already checked that the retainCount is 2 at some point, but stepping through and seing how it changes could shed some new light on what's going on. If everything is as it seems, put a breakpoint on the method that crashes later on, start debugging from the beginning, notice the address of the array object when it's created, then continue to the crashing method and see if the address is the same.

If you're not sure how to do this debugging, it's well worth the time to learn how by studying the documentation and searching for tuturials and such. Most of it can also be done by logging stuff, but it's usually easier and faster to just use the debugger.

Kelmon
Jul 31, 2006, 04:16 PM
How do you create and fill the NSMutableArray instance in point 2?

I'm using the NSArray method subArrayWithRange:(NSRange)range. This seems to work pretty well in that I can pass to each custom object created just the right amount of data that it needs. As noted earlier, the contents of the NSMutableArray is a collection of NSString objects that were read in earlier from a CSV file.

I'll try debugging but the debugger keeps crashing on me at the moment and I'm not taking that to be a good sign...

gekko513
Jul 31, 2006, 04:23 PM
I don't see subArrayWithRange returning an NSMutableArray, it seems to return an NSArray.

Kelmon
Jul 31, 2006, 04:41 PM
I don't see subArrayWithRange returning an NSMutableArray, it seems to return an NSArray.

That turned out not to be a problem because the custom objects was actually expecting an instance of NSArray anyway. Further, it turns out that the root cause of the problem was due to a bad refactoring of code in that I had references to another instance of NSArray that I'd been using in an old (and very bad) approach to the problem and the application was using references to this array rather than the new one (which only passed a subset of NSString objects).

Needless to say that the resolution of this bug has just lead me to a new one (Yay! Infinite loop...) but at least this is progress.

To be honest this whole refactoring job was simply to try and cure a bad memory leak problem that plagued my first attempt. It was OK generating a few hundred XML files but when it was being asked for thousands I quickly discovered the application consuming all my system memory. Ho hum. The first version may have been badly designed, had a bunch of memory leaks, but at least it produced output...

Sometimes I wonder why I left Java for Objective-C. I must be nuts.

gekko513
Jul 31, 2006, 04:45 PM
Sometimes I wonder why I left Java for Objective-C. I must be nuts.
Yeah, I did, too. Well, I haven't left Java, but I came from Java. I like Objective C, but I'm probably still more efficient when programming in Java since I know the Java frameworks so much better than Cocoa.

Kelmon
Jul 31, 2006, 04:51 PM
Oh, for an Objective-C garbage collector...

HiRez
Jul 31, 2006, 05:00 PM
Oh, for an Objective-C garbage collector...It's coming (http://arstechnica.com/staff/fatbits.ars/2006/5/6/3868). Possibly with Leopard, we should know next week.

gekko513
Jul 31, 2006, 05:01 PM
At least there is a consistent system on how to do the memory management in Cocoa. With C++ it's worse. There are libriaries out there to help you but if you don't control all the code yourself, there's no telling what kind of scheme you may encounter.

Kelmon
Jul 31, 2006, 05:45 PM
It's coming (http://arstechnica.com/staff/fatbits.ars/2006/5/6/3868). Possibly with Leopard, we should know next week.

Crickey! If this is true then I'm going to need to check outside for snow and some jolly fat bloke delivering presents...

Kelmon
Jul 31, 2006, 05:49 PM
Well, my program is more or less running now. There's still memory leaks but it's not as bad as before. However, the sodding Autorelease mechanism is causing the application to crash once the final XML document has been generated, so any suggestions on how I can do pro-active releasing of objects created in an IBAction method (i.e. one in an event loop) without causing the Autorelease to cause a crash would be most welcome. At a size of roughly 20KB per object (which is written to the resultant XML file) and a desire to create around 10,000 such files, I need to be releasing the memory taken up as soon as possible. I certainly can't wait around for the Autorelease to do it's thing...

Kelmon
Jul 31, 2006, 06:29 PM
OK, this is the dardest thing. At the moment my application seems to be blocking as soon as the main window has loaded. The window itself appears to be greyed-out the application is entirely unresponsive; no errors reported. The logging that I have in place indicates that setup of the AppController object was successfully completed and that the application should now be awaiting input from the user. I haven't changed anything recently with the initialisation code and this seems to be something that has occurred suddenly. Anyone else come across this before and, if so, what were you able to do about it?

gekko513
Jul 31, 2006, 06:35 PM
Well, my program is more or less running now. There's still memory leaks but it's not as bad as before. However, the sodding Autorelease mechanism is causing the application to crash once the final XML document has been generated, so any suggestions on how I can do pro-active releasing of objects created in an IBAction method (i.e. one in an event loop) without causing the Autorelease to cause a crash would be most welcome. At a size of roughly 20KB per object (which is written to the resultant XML file) and a desire to create around 10,000 such files, I need to be releasing the memory taken up as soon as possible. I certainly can't wait around for the Autorelease to do it's thing...
You should be able to rewrite it so that the large construct you use are created by you with alloc instead of having them returned from other methods. Objects created with [[SomeClass alloc] someInitMethod] (or new) don't have autorelease called on them and can and must be released manually.

Kelmon
Jul 31, 2006, 06:43 PM
Sounds a lot like what I am doing. The XML files are created by my custom objects that are being instantiated using the [[MyClass alloc] init] approach and then explicitly released as soon as possible but the Autorelease still seems to want to muck things up.

Mind you, all this is immaterial at the moment since my application appears to be dead. Start up is fine but as soon as the mainMenu nib displays, dead application time. Deleting and re-adding the AppController object, plus re-wiring the interface, seems not to have improved the situation. Frankly, I have no idea what is happening at the moment.

kainjow
Aug 1, 2006, 01:17 AM
If you want to post your project somewhere, I (or I'm sure someone else) would be happy to check it out.

On the topic of Cocoa memory management... it's beautiful. Once you get it, it makes sense 100% and is perfectly logical. I love the control Cocoa gives you.

Also, a quick way to turn an NSArray into an NSMutableArray is the mutableCopy method (be sure to release or autorelease it though). Comes in handy often.

Kelmon
Aug 1, 2006, 02:16 AM
OK, the cause of my application not starting has been found but I don't understand why this is a problem. The upshot of the problem code is as follows:

"As part of the AppController init method an attempt to read in an XML file to an instance of NSXMLDocument is made. The file is successfully read into the allocated instance of NSXMLDocument and processing continues. Once the init method is completed the application effectively stops loading - the window, complete with widgets setup by the awakeFromNib method, are displayed but the whole thing is greyed-out and cannot be interacted with at all; Force Quitting the application is the only way to close it."

What really bugs the heck out of me on this one is that the load of the XML document is not only successful but that this is a section of code that had been working fine.

Anyway, the code (which I basically copied from Apple's documentation) is as follows:


// Find the XML Template file stored in the main application bundle
NSBundle *bundle = [NSBundle mainBundle];
NSString *pathToXML = [bundle pathForResource:@"XMLDocTemplate" ofType:@"xml"];

// Attempt to create NSURL object from file path
NSURL *furl = [NSURL fileURLWithPath:pathToXML];
if (!furl) {
NSLog(@"Unable to open path as URL");
}

// Set pointer for errors
NSError *err = nil;

// Attempt to create new NSXMLDocument from template file
xmlTemplate = [[NSXMLDocument alloc] initWithContentsOfURL:furl
options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA)
error:&err];

// If load failed, try again with different options
if (xmlTemplate == nil) {
xmlTemplate = [[NSXMLDocument alloc] initWithContentsOfURL:furl
options:NSXMLDocumentTidyXML
error:&err];
}

// Check if an error was recorded
if (err) {
[self handleError:err];
}

kainjow
Aug 1, 2006, 02:21 AM
Did you try commenting that code out and see if the same behavior exists?

Kelmon
Aug 1, 2006, 02:38 AM
Did you try commenting that code out and see if the same behavior exists?

Basically, my troubleshooting of the problem saw me commenting out the complete init method and the helper method that reads in the XML file (putting the code for the helper method directly into the init method also didn't help), verifying that the application GUI at least launches (no problem) and then gradually re-enabling code until the problem section was found. Specifically, the problem lines of code appears to be the two that actually read in the XML file into the NSXMLDocument instance. Comment those lines of code out and everything works great, although the application is now useless since the XML file is key.

I've posted the Xcode project to my iDisk so if you wouldn't mind taking a look at it then that would be most appreciated. The file can be accessed here:

http://idisk.mac.com/mpeaston-Public?view=web

My main wish at the moment is simply to get the project to load the XML document without effectively hanging. After that it would be great to plug the memory leaks but just getting the application running again would be an improvement.

Please note that this is my first home-brew application and therefore I appreciate that the design and implementation will no doubt be bad. Suggestions on improvements are much welcome but try to be kind about it since my confidence at 3am last night pretty much hit rock bottom. My wife wasn't much impressed either...

caveman_uk
Aug 1, 2006, 02:52 AM
Oh, for an Objective-C garbage collector...
The secret is only use alloc or copy when you really need to. There are often other methods that give autoreleased objects that are probably a better choice and you don't need to remember to explicitly release them. I generally only use alloc for those objects that need to hang around longer than the end of a method.

A garbage collector will probably only slow things down. I hope that you can turn it off as a compiler option.

caveman_uk
Aug 1, 2006, 02:56 AM
Basically, my troubleshooting of the problem saw me commenting out the complete init method and the helper method that reads in the XML file (putting the code for the helper method directly into the init method also didn't help), verifying that the application GUI at least launches (no problem) and then gradually re-enabling code until the problem section was found. Specifically, the problem lines of code appears to be the two that actually read in the XML file into the NSXMLDocument instance. Comment those lines of code out and everything works great, although the application is now useless since the XML file is key.

If you need to read a file on launch the best thing to do is to set up an object as the app delegate (using [NSApp setDelegate: object] or use IB) and then do the reading code in -applicationDidFinishLoading. I'll try to have a look at your code later. I've used the NSXML stuff in my own code so I have experience of getting it to work ;)

kainjow
Aug 1, 2006, 03:06 AM
Kelmon, I checked out the source, and I have no idea why it's not working.

However, I came up with a work around :)

Replace this line:
[self setXmlTemplate:[[NSXMLDocument alloc] initWithContentsOfURL:furl options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA) error:&err]];
With this:
[self setXmlTemplate:[[[NSXMLDocument alloc] initWithData:[NSData dataWithContentsOfURL:furl] options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA) error:&err] autorelease];

Kelmon
Aug 1, 2006, 03:16 AM
Kelmon, I checked out the source, and I have no idea why it's not working.

However, I came up with a work around :)

Replace this line:
[self setXmlTemplate:[[NSXMLDocument alloc] initWithContentsOfURL:furl options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA) error:&err]];
With this:
[self setXmlTemplate:[[[NSXMLDocument alloc] initWithData:[NSData dataWithContentsOfURL:furl] options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA) error:&err] autorelease];

Well, I gave the suggestion a whirl but it's still not running. Were you able to get the application to work using the suggestion yourself as it would be damned strange if it worked for you but not me. That said, I"m finding the whole thing damned strange at the moment, plus more than a little frustrating.

Kelmon
Aug 1, 2006, 03:24 AM
If you need to read a file on launch the best thing to do is to set up an object as the app delegate (using [NSApp setDelegate: object] or use IB) and then do the reading code in -applicationDidFinishLoading. I'll try to have a look at your code later. I've used the NSXML stuff in my own code so I have experience of getting it to work ;)

This is an approach that I am not familiar with but am willing to give it a bash. However, can you clarify for me how the delegate object of NSApplication would set the NSXMLDocument object created such that the AppController class can pass it (or a copy of it) as an argument to the init method of instances of my custom class for generating the final XML file? I can see how to setup the delegate and use of the -applicationDidFinishLoading method but the links between NSApplication, AppController and AppDelegate is eluding me at the moment.

caveman_uk
Aug 1, 2006, 03:30 AM
The delegate can be the AppController so if you were doing it in code you could have in the AppController's init method you'd have the line [NSApp setDelegate: self] then, when the app had fully loaded and everything else was up and waiting the AppControllers -applicationDidFinishLoading method would be called. The problem sometimes with using init and awakeFromNib is that you don't know what's been instantiated or what order. (nibs etc) If you use applicationDidFinishLoading then you KNOW everything's loaded.

Kelmon
Aug 1, 2006, 04:33 AM
The delegate can be the AppController so if you were doing it in code you could have in the AppController's init method you'd have the line [NSApp setDelegate: self] then, when the app had fully loaded and everything else was up and waiting the AppControllers -applicationDidFinishLoading method would be called. The problem sometimes with using init and awakeFromNib is that you don't know what's been instantiated or what order. (nibs etc) If you use applicationDidFinishLoading then you KNOW everything's loaded.

I confess that I did the setup in Interface Builder but, ladies and gentlemen, we have a winner. The application is now at least starting up properly. Now all I need to do is stop the damned thing from crashing all the time.

Given the current stack trace ending with a message to objc_msgSend_rtp which was proceeded by NSPopAutoreleasePool, it looks like my old friend the Autorelease pool is trying to screw things up again. The upshot of the process is as follows and, if you've downloaded the code you can follow along:

1. The user enters an integer value representing the number of primary XML documents to create. The application, when it's working correctly, is expected to create, for each primary XML document, 5 "shipments" that each will generate 3 sub XML documents. For example, if the user enters 10 as the number of primary documents the application is expected to create 10 + ( 10 x 5 x 3 ) documents, or 160 XML files in total.

2. The IBAction method takes the user's input and, in a While Loop, creates the 10 primary XML documents (instances of MMTriggerXMLDoc). In the initWith... method for the primary XML document a number of arguments are passed, including an instance of NSDictionary and NSXMLDocument (the NSXMLDocument now being created in the delegate method of AppController). The uses accessor methods to store these arguments as local variables and sends the received object instances a retain message. The exception to this is for the instance of NSXMLDocument and maybe this where my problem lies. Since the NSXMLDocument created during application initialisation is meant to be a template the instance of MMTriggerXMLDoc creates a copy of the NSXMLDocument using the -copy:(NSXMLDocument)aDocument method. A reasonable amount of the setup of the object is performed using the initWith method of the superclass to MMTriggerXMLDoc, MMXMLDoc. Once the basic setup of the instance of MMTrigerXMLDoc is complete the object changes a number of the NSXMLElement values that makes it unique using a utility method I created in the superclass, MMXMLDoc.

3. As part of the setup process for the primary XML document, the sub documents required for that particular primary document are created (instances of MMSubXMLDoc) and stored in a local instance of NSMutableArray. Again, much of the setup is performed by the superclass, MMXMLDoc, and the NSXMLDocument created is again a copy of the original NSXMLDocument object loaded by the application at launch.

4. Once setup is complete the AppController object (still in the IBAction method's While Loop) requests the instance of MMTriggerXMLDoc to write itself to the file system using the -writeXMLDocumentToFileInDirectory:(NSString *)directoryName method). In MMTriggerXMLDoc this method is an overridden version of one defined in the superclass and it is used to loop through the instance of NSMutableArray that holds the sub XML documents and sends each the -writeXMLDocumentToFileInDirectory:(NSString *)directoryName message. Once this loop is completed the method calls the superclass implementation in order to write itself out to the file system.

It is when the instance of MMTriggerXMLDoc (from the writeXMLDocumentToFileInDirectory method of the superclass MMXMLDoc) executes the following statement that the crash occurs:

NSData *xmlData = [newXMLDoc XMLDataWithOptions:NSXMLNodePrettyPrint];

The output in the log for the application shows the following lines of interest:

2006-08-01 10:50:54.510 MixAndMatchTesting[11860] *** -[NSCFString _XMLStringWithOptions:appendingToString:]: selector not recognized [self = 0x44f7780]
2006-08-01 10:50:54.513 MixAndMatchTesting[11860] *** -[NSCFString _XMLStringWithOptions:appendingToString:]: selector not recognized [self = 0x44f7780]

I'm going to take from this that the Autorelease Pool has released elements in the NSXMLDocument since the local instance variable, newXMLDoc is still available, according to the debugger. Any idea how or why this might occur?

Kelmon
Aug 1, 2006, 04:51 AM
If it helps I have posted the current version of the Xcode project to my iDisk again as Mix&MatchTesting2.zip. This archive can be accessed here (http://idisk.mac.com/mpeaston-Public?view=web).

Posting these copies of my project is perhaps the most use I've gotten from my iDisk recently...

whooleytoo
Aug 1, 2006, 10:58 AM
It's coming (http://arstechnica.com/staff/fatbits.ars/2006/5/6/3868). Possibly with Leopard, we should know next week.

Yikes, I can't believe I hadn't heard about this before now. Nice to actually have a developer-related piece of news to announce at the developer conference!

Kelmon
Aug 2, 2006, 04:09 AM
OK, given that the problem encountered in the application at present is memory related rather than a problem passing an argument I have opened a new topic that can be found here (http://forums.macrumors.com/showthread.php?t=221084). Thanks to everyone who helped out with the initial problems in my application and I really hope that you can continue to help with the new problem.