0
votes

i've been dancing with a tambourine for a while, but still don't know what's the reason for that error. I've got a tableView with history of user queries data from sqlite base. I'm new to iPhone developing, so my code may be a bit excessive. The hierarchy is:

  • HistoryModel model-object with some init methods

  • HistoryDataController gets data from database and presents an array of HistoryModel objects

  • HistoryViewController subclass of UITableView, displays data

  • AppDelegate there i initially store an array of HistoryModel objects (by getting it from HistoryDataController) for HistoryViewController to access it.

The problem is, when i scroll the table or open the tab with it for the second time - it crashes with -[CFString retain]: message sent to deallocated instance

Code:

HistoryModel.h pretty unnecessary class for that case, but i want that worked to repeat in several identical cases, but a bit more complicated

@interface HistoryModel : NSObject {
    int entry_id;
    NSString *word;
}

- (id)initWithWord:(NSString *)word;
- (id)initWithWord:(NSString *)word andId:(int)entry_id;

@property int entry_id;
@property (retain) NSString *word;

@end

HistoryModel.m

@implementation HistoryModel

@synthesize entry_id, word;

- (id)initWithWord:(NSString *)_word {
    [super init];
    word = _word;
    return self;
}

- (id)initWithWord:(NSString *)_word andId:(int)_entry_id {
    entry_id = _entry_id;
    return [self initWithWord:_word];

@end

HistoryDataController.h i use the entity of that class as getter of data and a storage for HistoryModel objects (in historyEntries property)

@interface HistoryDataController : NSObject {
    NSMutableArray *historyEntries;
    int limit;
}

@property (nonatomic, retain) NSMutableArray *historyEntries;
@property int limit;

- (id)initWithHistoryData;
- (id)initWithHistoryDataLimitedBy:(int)limit;

HistoryDataController.m

@implementation HistoryDataController
@synthesize historyEntries, limit;

- (id)initWithHistoryDataLimitedBy:(int)_limit {
    [super init];

    // Getting data from database
    {some DB stuff}

    NSMutableArray *tmp_historyEntries = [[NSMutableArray alloc] init];
    while(result == SQLITE_ROW)
    {
        HistoryModel *currentHistoryEntry = [[HistoryModel alloc] initWithWord:[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)] ];
        [tmp_historyEntries addObject:currentHistoryEntry];
        result = sqlite3_step(statement);
    }
    historyEntries = tmp_historyEntries;

    {some DB stuff}
    return self;
}
@end

HistoryViewController.h subclass of UITableViewController, gets data stored in AppDelegate's property and displays in the table

@interface HistoryViewController : UITableViewController {
    IBOutlet UITableView *historyTable;
    SynonymsAppDelegate *appDelegate;
}

@property (retain) UITableView *historyTable;

@end

HistoryViewController.m

@implementation HistoryViewController
@synthesize historyTable, historyEntriesToShow;

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    appDelegate = (SynonymsAppDelegate *)[[UIApplication sharedApplication] delegate];
    [appDelegate initHistoryList];
    [self.tableView reloadData];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    {standart cell stuff}

    HistoryModel *historyEntry = [appDelegate.historyList objectAtIndex:indexPath.row];
    cell.textLabel.text = historyEntry.word;
    return cell;
}

@end

SynonymsAppDelegate.h when history tab opens, it gets data of historyList property, that was formed by HistoryDataController :)

@interface SynonymsAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
    ...
    NSMutableArray *historyList;
}
...
@property (retain) NSMutableArray *historyList;

- (void)initHistoryList;

@end

SynonymsAppDelegate.m

@implementation SynonymsAppDelegate
@synthesize window, tabBarController, historyList;

- (void)initHistoryList {
    HistoryDataController *historyDataController = [[HistoryDataController alloc] initWithHistoryData];
    historyList = historyDataController.historyEntries;
}

@end

Fuf. Sorry for so much code, but i believe that's all necessary. As a result of half the day spent on this question, i may guess, that problem is somehow connected with HistoryModel entity, because when i delete "retain" for word @property, the error switches for -[CFString isEqualToString:]: message sent to deallocated instance

I'm not really experienced in memory management, but i guess this HistoryModel objects inside historyEntry in HistoryViewController or in historyList in AppDelegate releases somehow, when scrolling the table or opening the tab for the second time. But this's just my guessing. Really appreciate the help.

1
Pretty long, yet important parts are missing. For NSString properties, you almost always want (copy) instead of retain. More code for your HistoryModel might be useful. Probably the code is elsewhere, though - maybe a release to an autoreleased NSString. - Eiko
Don't know if this is the problem, but I think you need a retain in your appDelegate: historyList = [historyDataController.historyEntries retain]; - Rengers
Thanks, i tried all you've proposed, still that has't solved the problem. For Eiko: that's all code of HistoryModel i've got, except for init methods, which, as i mentioned, aren't used anyway. Rengers, i tried your solution, it didn't help but i'll try to retain other objects. In fact, i haven't done memory management at all yet, cuz i'm not experienced in it and this is just the very beginning of the program. - nikans
... So it's very unlikely that i've released any of objects manually. - nikans
It's hard to tell what's going wrong from this cod3, but what I would do is make sure you use the synthesized getter and setter everywhere possible. For example in HistoryDataController.m, do self.historyEntries = tmp_historyEntries. Or better yet, skip the whole tmp_historyEntries. But as you've stated, you didn't release anything manually so it's unlikely this would fix it. Still, it's good practice :) - Rengers

1 Answers

1
votes

You definitely have an issue in your -[HistoryModel initWithWord] You should retain (or better yet copy) the string that is being passed.

I would write it like this:

- (id)initWithWord:(NSString *)_word {
    [super init];
    self.word = _word; // this is same as [self setWord:_word]
    return self;
}

There are some who would say using the setter in your init is not a good practice. I'm not of that camp. But in any case, you need to be retaining or copying that string.

Then you have a similar issue in your app delegate where you are leaking each HistoryDataController as you create a new one. (and that happens every time that tableview appears). And you really should be retaining that array as well (although that hasn't caused a problem yet because you're leaking the HistoryDataControllers and therefore masking that issue so far.)

My general advice to you would be don't put off memory management. To come back later and try to get it right is complicated and error-prone even for an experienced developer. It is much, much easier to build the correct memory management techniques into the code as you write it. This means it's well worth your time to read the memory management guide first before you start coding something like this. You'll save yourself a lot of time and frustration.