таблица прокрутки: сообщение, отправленное в освобожденный экземпляр

#iphone #objective-c #uitableview #memory-management

#iPhone #objective-c #uitableview #управление памятью

Вопрос:

я некоторое время танцевал с бубном, но все еще не знаю, в чем причина этой ошибки. У меня есть TableView с историей пользовательских запросов данных из базы sqlite. Я новичок в разработке iPhone, поэтому мой код может быть немного перегружен. Иерархия является:

  • Модель-объект HistoryModel с некоторыми методами инициализации

  • HistoryDataController получает данные из базы данных и представляет массив объектов HistoryModel

  • Подкласс HistoryViewController UITableView отображает данные

  • AppDelegate там я изначально сохраняю массив объектов HistoryModel (получая его из HistoryDataController), чтобы HistoryViewController мог получить к нему доступ.

Проблема в том, что когда я прокручиваю таблицу или открываю вкладку с ней во второй раз — происходит сбой с -[CFString retain]: сообщение отправлено в освобожденный экземпляр

Код:

HistoryModel.h довольно ненужный класс для этого случая, но я хочу, чтобы это сработало, чтобы повторить в нескольких идентичных случаях, но немного сложнее

 @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
я использую сущность этого класса в качестве средства получения данных и хранилища для объектов HistoryModel (в свойстве historyEntries)

 @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
  

СинонимыAppDelegate.h
когда открывается вкладка истории, она получает данные свойства historyList, которые были сформированы HistoryDataController 🙂

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

- (void)initHistoryList;

@end
  

СинонимыAppDelegate.m

 @implementation SynonymsAppDelegate
@synthesize window, tabBarController, historyList;

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

@end
  

Фуф. Извините за такой объем кода, но я считаю, что это все необходимо.
В результате половины дня, потраченного на этот вопрос, я могу предположить, что проблема каким-то образом связана с объектом HistoryModel, потому что, когда я удаляю «сохранить» для word @property, ошибка переключается на -[CFString isEqualToString:]: сообщение, отправленное освобожденному экземпляру

Я не очень разбираюсь в управлении памятью, но я предполагаю, что эта HistoryModel объекты внутри historyEntry в HistoryViewController или в historyList в AppDelegate освобождаются при прокрутке таблицы или открытии вкладки во второй раз. Но это только мое предположение. Действительно ценю помощь.

Комментарии:

1. Отсутствуют довольно длинные, но важные части. Для свойств NSString вы почти всегда хотите (копировать) вместо сохранения. Может оказаться полезным дополнительный код для вашей исторической модели. Возможно, код находится в другом месте — возможно, релиз для автоматически выпущенной NSString .

2. Не знаю, проблема ли в этом, но я думаю, вам нужно сохранить в вашем AppDelegate: historyList = [Сохранить historyDataController.historyEntries];

3. Спасибо, я перепробовал все, что вы предложили, но это все равно не решило проблему. Для Eiko: это весь код HistoryModel, который у меня есть, за исключением методов инициализации, которые, как я уже упоминал, все равно не используются. Ренгеры, я попробовал ваше решение, оно не помогло, но я постараюсь сохранить другие объекты. На самом деле, я вообще еще не занимался управлением памятью, потому что у меня нет опыта в этом, и это только самое начало программы.

4. … Так что очень маловероятно, что я освободил какой-либо из объектов вручную.

5. Трудно сказать, что происходит не так из этого cod3, но что я бы сделал, так это убедился, что вы используете синтезированный геттер и сеттер везде, где это возможно. Например, в HistoryDataController.m выполните self.historyEntries = tmp_historyEntries . Или, еще лучше, пропустите все tmp_historyEntries целиком. Но, как вы уже заявили, вы ничего не выпускали вручную, так что маловероятно, что это исправит проблему. Тем не менее, это хорошая практика 🙂

Ответ №1:

У вас определенно есть проблема в вашем -[HistoryModel initWithWord] Вы должны сохранить (или, еще лучше, скопировать) передаваемую строку.

Я бы написал это так:

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

Есть некоторые, кто сказал бы, что использование параметра setter в вашем init не является хорошей практикой. Я не из этого лагеря. Но в любом случае вам необходимо сохранить или скопировать эту строку.

Затем у вас возникает аналогичная проблема в вашем делегате приложения, где вы пропускаете каждый HistoryDataController при создании нового. (и это происходит каждый раз, когда появляется tableview). И вам действительно следует сохранить и этот массив (хотя это пока не вызвало проблемы, потому что вы пропускаете HistoryDataControllers и, следовательно, пока маскируете эту проблему).)

Мой общий совет вам был бы таким: не откладывайте управление памятью. Вернуться позже и попытаться все исправить сложно и чревато ошибками даже для опытного разработчика. Намного, намного проще встроить правильные методы управления памятью в код по мере его написания. Это означает, что вам стоит сначала прочитать руководство по управлению памятью, прежде чем начинать кодировать что-то подобное. Вы сэкономите себе много времени и разочарований.

Комментарии:

1. Большое вам спасибо. Теперь, когда он работает относительно стабильно, я прочитаю некоторую информацию об управлении памятью и попытаюсь исправить эти проблемы. Но могу я спросить, что это значит «у вас аналогичная проблема в вашем делегате приложения, где вы пропускаете каждый HistoryDataController при создании нового». Я не до конца понял концепцию утечки ) Что я должен попытаться сделать, чтобы устранить эту проблему?

2. Если происходит утечка, это не работает. Это просто временно работает.:) Если вы посмотрите в свой -[SynonymAppDelegate initHistoryList], вы каждый раз создаете новый HistoryDataController. Но тогда вы не освобождаете его. Если все, что вам требуется от этого контроллера, — это массив, вы должны сохранить этот массив, а затем освободить контроллер. Надеюсь, это поможет.

3. Спасибо, я постараюсь применить это 🙂