Сбой при освобождении UIImage после предупреждения памяти - PullRequest
0 голосов
/ 16 февраля 2011

Я довольно новичок в разработке для iphone, и я столкнулся со странным сбоем в моем приложении.Действительно, мое приложение всегда падает после того, как я имитировал предупреждение памяти.Я могу воспроизвести это поведение каждый раз, и мне удалось изолировать неисправную строку:).

Я работаю в пользовательском UITableViewController, поставляя пользовательские UITableViewCells.

@implementation CustomTableViewController
// [...]
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{   
static NSString *CellIdentifier = @"MyTableViewCell";
UITableViewCell *cell = nil;

if ([indexPath row] < [dataList childCount])
{
    cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];

    if (nil == cell) 
    {
        cell = [[[KpowUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                          reuseIdentifier:CellIdentifier] autorelease];

        KUICustomView* customView = [[KUICustomView alloc]initWithFrame:CGRectZero];
        [(KpowUITableViewCell*)cell setFrontView:customView];
        [customView release];
    }

    KUICustomView* cView = [(KpowUITableViewCell*)cell frontView];
    [cView setDataObject:[dataList getChildAtIndex:[indexPath row]]]; // The crash happens in this function
}
// [...]

Вот функция, в которой я устанавливаю пользовательский объект данных для своего представления ячейки:

-(void)setDataObject:(DataObject *)do
{
    [do retain];
    [dataObject release];
    dataObject = do;

    NSString* defaultPath = [NSString stringWithFormat:@"%@/default_image.png", [[NSBundle mainBundle] resourcePath]];
    UIImage* defaultImage = [[UIImage alloc] initWithContentsOfFile:defaultPath];
    [self setImage: defaultImage];//[UIImage imageNamed:@"default_image"]]; // The crash happens in this function
    [defaultImage release];
    // [...]

И, наконец, вот где происходит волшебство:

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
    [image release]; // CRASH EXC_BAD_ACCESS
    image = img;

    [self setNeedsDisplay];
}

Итак, все нормально работает в обычном сценарии.Но если я имитирую предупреждение памяти, прокручиваю свой UITableView и все эти функции вызываются, приложение вылетает.Если я удаляю [релиз изображения], никакого сбоя (но «Hai там утечки памяти»).Вывод NSLog всегда выглядит примерно так:

setImage : old image > <UIImage: 0x4b54910>/UIImage/1

Я действительно не вижу, что я делаю неправильно или что я мог сделать, чтобыобойти эту проблему.Вот скриншот отладчика Xcode ...

http://img30.imageshack.us/i/debuggerscreen.png/

Любая помощь приветствуется.Заранее спасибо

Edit 1: @bbum Build and Analyze показал мне несколько не связанных предупреждений, но все же полезно.Даже не видел, что там было

Есть еще одно место, где я установил изображение.В setDataObject изображение является просто заполнителем.Я запускаю загрузку реального изображения асинхронно и возвращаю его обратно в requestDidFinishLoad.Метод выглядит следующим образом:

- (void)requestDidFinishLoad:(KURLRequest*)request
{
    if (request == currentRequest) 
    {
        UIImage* img = [[UIImage alloc] initWithData:[request data]];

        if (nil != img)
            [self setImage:img];

        [img release];
    }

    if (currentRequest == request)
        currentRequest = nil;
    [request release];
}

Я запустил инструменты с NSZombie Detection, и результат, похоже, указывает в другом направлении.Вот скриншот:

http://img13.imageshack.us/i/zombieinstrument.jpg/

Я пока не совсем уверен, что с этим делать, но расследование продолжается:)

Ответы [ 5 ]

4 голосов
/ 16 февраля 2011
[image release]; // CRASH EXC_BAD_ACCESS

Ваша обратная трассировка показывает, что вы столкнулись с ошибкой dealloc метода UIImage.Вы перевыпустили где-то image.

Сначала попробуйте «Построить и проанализировать» и посмотрите, не сработает ли какое-нибудь полезное предупреждение.Исправьте их.

Затем включите функцию обнаружения зомби и попытайтесь воспроизвести проблему.Это может дать подсказки.

Обратите внимание, что @property - это "просто" удобство для объявления пары методов установки / получения (или одного из них).[foo setImage:bar] в точности эквивалентен независимо от того, используете ли вы @property или объявляете метод напрямую.Точно так же foo.image = bar; в точности совпадает с [foo setImage:bar];.

Наконец, это все код, который обрабатывает ваш image?Как вы справляетесь с предупреждением о нехватке памяти?

Кроме того, было бы лучше, если бы ваш сеттер не позвонил setNeedsDisplay:.Используйте простые @property и @synthesize сеттер / геттер.Затем, когда вы вызываете сеттер, наберите setNeedsDisplay: на линии после.Это позволяет отделить пользовательский интерфейс от необходимости определять, когда должен появиться дисплей.

Aha!Твой зомби был очень полезен.В частности, похоже, что вы преждевременно освобождаете соединение URLC, что приводит к слишком быстрому освобождению данных NSData.Это может быть источником вашей проблемы или, по крайней мере, должно быть исправлено перед попыткой этой проблемы.

1 голос
/ 17 февраля 2011

Эврика!Я наконец нашел то, что я сделал не так.Когда изображение загружается асинхронно, оно использует данные, поступающие из пользовательского объекта, используемого для кэширования, хранящегося в диспетчере кэша.При выдаче предупреждения о памяти менеджер кэша освобождает все, удаляя объект кэша из памяти.Вот как мой dealloc выглядел в моем «кэшируемом объекте»:

-(void)dealloc
{
    // [...]
    [data dealloc];
    // [...]
}

Да, я явно вызывал dealloc ... Поэтому, конечно, когда UIImage хотел выпустить свой собственный указатель на данные, это не удалось...

Я чувствую себя так глупо ^^.(Мне всегда трудно отлаживать свои собственные программы, так как иногда я воспринимаю части кода как «ОК», и я даже не думаю смотреть туда ...)

Итог: NSZombie был действительно полезен (спасибо @bbum), чтобы выяснить настоящего преступника.И никогда (?) Никогда не вызывать dealloc явно .

(есть ли в любом случае «закрыть» этот вопрос?

0 голосов
/ 17 февраля 2011

В частности, чтобы исправить неправильное представление @ sabby:

-(void)setImage:(UIImage *)img
{
    [img retain]; // img rc +1
    image = img; 
    if(image)
    {
        [image release]; // img rc -1
    }
    // image set, but not retained by `self`
}

Конечный результат? Метод установки, который не сохраняет набор элементов. Объедините это с:

- initWithImage:anImage
{
     if (self=[super init]) {
           image = [anImage retain];
     }
     return self;
}

Вышеуказанный установщик утечет исходное изображение, переданное init, перевыпустит любое изображение, переданное setImage:, и, если приложение выживет достаточно долго, вполне вероятно, что произойдет сбой:

- (void) dealloc
{
    [image release];
    [super dealloc];
}
0 голосов
/ 16 февраля 2011

Вы, вероятно, выпускаете изображение в ответ на предупреждение памяти, но также не устанавливаете его в ноль. Поэтому, когда вы вызываете setImage:, вы освобождаете уже освобожденный объект, что приводит к сбою. Попробуйте что-то вроде:

- (void)viewDidUnload {
    [super viewDidUnload];
    [image release]; image = nil;
}

или если image объявлено как свойство

- (void)viewDidUnload {
    [super viewDidUnload];
    self.image = nil;
}
0 голосов
/ 16 февраля 2011

Сделайте так, возможно, вам это поможет

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
     // CRASH EXC_BAD_ACCESS
    image = img;
if(image)
{
[image release];
}

    [self setNeedsDisplay];
}

Я не проверял это сам, но причиной вашего сбоя является выпуск изображения.

...