#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. Спасибо, я постараюсь применить это 🙂