views:

167

answers:

2

I am working on an app that has several views selected using a tab bar. One tab displays a list of data using a table view. Another tab uses a table view (and a navigation controller) so the user can add items to/delete items from the list.

If the user switches to the first tab after adding or deleting items on the other tab, the app crashes. (I'm still working on trying to glean anything useful from the debugger output).

New (as of 22 Aug) info:

In light of some of the comments below, I've been doing some exploring and I think I must not be allocating objects correctly. Below is a stack trace, but note I am not always getting the same error. Eg, sometimes I get an uncaught exception, sometimes just the debugger steps in; when it is an uncaught exception, it's not always the same one.

2009-08-22 15:20:34.254 Mexico[29531:20b] * -[_NSIndexPathUniqueTreeNode isEqualToString:]: unrecognized selector sent to instance 0x562ff0 2009-08-22 15:20:34.255 Mexico[29531:20b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[_NSIndexPathUniqueTreeNode isEqualToString:]: unrecognized selector sent to instance 0x562ff0' 2009-08-22 15:20:34.257 Mexico[29531:20b] Stack: (

    2504683691,
    2423127611,
    2504712874,
    2504706220,
    2504706418,
    817812590,
    19892,
    816386359,
    816387412,
    816468754,
    816411067,
    836579268,
    836579060,
    836577406,
    836576671,
    2504177986,
    2504184740,
    2504187000,
    827745792,
    827745989,
    816114848,
    816160924
)
[Session started at 2009-08-22 15:20:34 -0400.]
Loading program into debugger…
GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".warning: Unable to read symbols for "/System/Library/Frameworks/UIKit.framework/UIKit" (file not found).
warning: Unable to read symbols from "UIKit" (not yet mapped into memory).
warning: Unable to read symbols for "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" (file not found).
warning: Unable to read symbols from "CoreGraphics" (not yet mapped into memory).
Program loaded.
sharedlibrary apply-load-rules all
Attaching to program: `/Users/matt/Library/Application Support/iPhone Simulator/User/Applications/0F9033CE-39BB-4589-B791-5E473D991789/Mexico.app/Mexico', process 29531.
(gdb) bt
#0  0x954a6f54 in ___TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION___ ()
#1  0x906dfe3b in objc_exception_throw ()
#2  0x9542da53 in CFRunLoopRunSpecific ()
#3  0x9542dc78 in CFRunLoopRunInMode ()
#4  0x31566600 in GSEventRunModal ()
#5  0x315666c5 in GSEventRun ()
#6  0x30a4eca0 in -[UIApplication _run] ()
#7  0x30a5a09c in UIApplicationMain ()
#8  0x00001ffc in main (argc=1, argv=0xbfffe108) at /Users/matt/Coding/iPhone/Mexico/main.m:13
(gdb) 

Here is some (I think) relevent code. First, the function that invokes a view to add a new entry:

- (IBAction)addButtonWasPressed {
    AddPlayerViewController *apvController;
    apvController = [[AddPlayerViewController alloc] 
         initWithNibName:@"AddPlayerView" bundle:nil];
    apvController.rvController = self;
    [self.navigationController pushViewController:apvController animated:YES];
    [apvController release];
}

Then in the add player view:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [rvController addPlayerNamed:textField.text];
    [textField resignFirstResponder];
    [self.navigationController popViewControllerAnimated:YES];
    return YES;
}

Finally RosterViewController is a subclass of UITableViewController and has the following method:

- (void)addPlayerNamed:(NSString *)name {
    Player *player = [[Player alloc] initWithName:name];
    [players addObject:player];  
    // insert NSLogS
    NSIndexPath *indexPath;
    indexPath = [NSIndexPath indexPathForRow:[players indexOfObject:player] inSection:0];
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:NO];
}

If I iterate over players and print out the names using NSLog statements (see commented spot above), then the app crashes after I add a second entry. If I take the NSLog statements out, then it won't crash until later.

+1  A: 

If i had to guess, i would say you are probably sharing the data objects (data sources) in your two table views and deleting from one is affecting the other, and you try to do something with the released object in the other view which is causing the app to crash, or something along those lines.

Daniel
+1  A: 

Here's how I'd do it:

  • Create a class that implements the UITableViewDataStore delegate methods. Assign it to something accessible from both viewcontrollers. A good place is up at the UIApplication level so you can get to it via something like:

    MyDataStore* store = ((MyApplication *)[UIApplication sharedApplication]).{myDataStore}

  • For backing store this class would keep the actual values in an array, a dictionary, a SQLite database, or something like that.

  • Assign this class to each table's dataSource property. Since it's owned by the app it shouldn't be released until the app is finished. So you'll want to make sure the object is accessible via a property with a "retain" attribute.

  • Each time the user adds something you'll want to add it to this object then call reloadData on the table to refresh the table.

  • For each of those views implement the viewWillAppear method. This gets called whenever that view comes into view. Inside it make a call to the table reloadData as well. This way when the user switches from one view to another, before the new view is shown, it will have refreshed itself with the new data.

  • If you assign the datastore object to the application then it owns it for the lifetime of the app so you probably don't want to set it to autorelease. If memory becomes an issue then you'll want to save the backing stuff to SQLite or CoreData.

You're probably already doing all this but this is the general pattern for creating shared data between views. Maybe it'll help jog something.

Ramin
Although viewWillAppear was right there in the template (although commented out---no excuse on my part :) I kept overlooking it. So consequently I had my insert/delete routines calling reloadData on both the showing table and the one that wasn't shown for every single edit. Big overkill! Other than that, I pretty much have what you outlined above. Having somebody else sketch out a similar solution does boost my confidence! Thanks!Still crashing, but I think I'm getting there---more details in the comment I'm about to add to David Maymudes's comment above.
Matt
In addPlayerNamed you're always adding the player to the end of the array, right? You can take out all the lines from "// insert NSLogS" line down and replace it with [table reloadData] once the item has been added to the backing array. This triggers a call back to numberOfRowsInSection (which should be returning the count of items in 'players') and then cellForRowAtIndexPath picks up the new row. Doesn't look like you care about inserting things in the middle of the table so you might as well take out all that extra stuff. Guessing that's where the crash happens.
Ramin
Ok, it was a memory problem with the Player class.
Matt