NSTimers, вызывающие утечки

Я много читал о NSTimers, но я, должно быть, делаю с ними что-то очень неправильное, потому что это практически все утечки, которые обнаруживаются в Leaks Instrument. В столбце «Ответственный кадр» указано -[NSCFTimer или +[NSTimer(NSTimer).

Итак, вот как у меня настроен NSTimer в главном меню. Я сократил его, чтобы просто показать, как настраивается таймер.

.h -

@interface MainMenu : UIView {
    NSTimer *timer_porthole;    
}

@end


@interface MainMenu ()

-(void) onTimer_porthole:(NSTimer*)timer;


@end

.m -

(в initWithFrame)

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {

        timer_porthole = [[NSTimer scheduledTimerWithTimeInterval:.05
                                                           target:self
                                                         selector:@selector(onTimer_porthole:)
                                                         userInfo:nil
                                                          repeats:YES] retain];

    }
    return self;
}   

При выходе из представления он убивает таймеры:

-(void) kill_timers{
     [timer_porthole invalidate];
     timer_porthole=nil;
}

И, конечно же, делолок:

- (void)dealloc {
    [timer_porthole invalidate];
    [timer_porthole release];
    timer_porthole = nil;

    [super dealloc];
}

person Chewie The Chorkie    schedule 02.03.2011    source источник


Ответы (3)


Не звоните в свой NSTimer!

Я знаю, что это звучит нелогично, но когда вы создаете экземпляр, он автоматически регистрируется в текущем (вероятно, основном) цикле выполнения потоков (NSRunLoop). Вот что Apple говорит по этому поводу...

Таймеры работают в сочетании с циклами выполнения. Чтобы эффективно использовать таймер, вы должны знать, как работают циклы выполнения — см. Руководство по программированию NSRunLoop и Threading. В частности, обратите внимание, что циклы выполнения сохраняют свои таймеры, поэтому вы можете сбросить таймер после того, как добавили его в цикл выполнения.

После планирования в цикле выполнения таймер срабатывает с указанным интервалом до тех пор, пока он не станет недействительным. Неповторяющийся таймер становится недействительным сразу после срабатывания. Однако для повторяющегося таймера вы должны сами аннулировать объект таймера, вызвав его метод аннулирования. Вызов этого метода запрашивает удаление таймера из текущего цикла выполнения; в результате вы всегда должны вызывать метод invalidate из того же потока, в котором был установлен таймер. Отключение таймера немедленно отключает его, чтобы он больше не влиял на цикл выполнения. Затем цикл выполнения удаляет и освобождает таймер либо непосредственно перед возвратом метода недействительности, либо в какой-то более поздний момент. После признания недействительными объекты таймера нельзя использовать повторно.

Таким образом, ваш экземпляр становится...

timer_porthole = [NSTimer scheduledTimerWithTimeInterval:.05
                                                           target:self
                                                       selector:@selector(onTimer_porthole:)
                                                         userInfo:nil
                                                          repeats:YES];

И теперь, когда вы больше не держите ссылку на экземпляр, вам не нужен вызов освобождения в вашем методе Dealloc.

person mmccomb    schedule 02.03.2011
comment
Да и нет. Представьте себе ситуацию, когда ваш таймер имеет какое-то отношение к контроллеру представления A, он должен срабатывать через 10 секунд, а пользователь перешел на другой контроллер представления через 5 секунд, и вам нужно аннулировать этот таймер, потому что он сработает через 5 секунд, и вы делаете не хочу, чтобы он стрелял, например. - person zrzka; 02.03.2011
comment
Это актуальная проблема, и, как вы указали в своем собственном ответе, с управлением памятью OP происходит несколько странных вещей. Но для меня наиболее очевидным источником утечки здесь является лишний ретейн. - person mmccomb; 02.03.2011
comment
По какой-то причине я думал, что мое приложение зависает без сохранения, и я обнаружил, что проблема в этом... кажется, что теперь все не протекает. Спасибо! - person Chewie The Chorkie; 02.03.2011
comment
... да - я упомянул об этом в своем ответе :) - person mmccomb; 02.03.2011
comment
Ладно, подождите, теперь вылетает, когда я захожу в определенные разделы. Он не показывает утечки, и это здорово... но сбои... почему? Пожалуйста помоги. 'NSInvalidArgumentException', причина: '-[__NSCFData недействительна]: в экземпляр отправлен нераспознанный селектор - person Chewie The Chorkie; 02.03.2011
comment
Подождите.. Я думаю, это потому, что я вызываю освобождение. Я не думаю очевидно. - person Chewie The Chorkie; 02.03.2011

Я видел, что вы уже приняли ответ, но здесь есть две вещи, которые я хотел исправить:

  1. нет необходимости сохранять запланированный таймер, но это не приносит никакого вреда (если вы отпустите его, когда он больше не нужен). «Проблемная» часть отношения таймер/цель заключается в том, что...
  2. таймер сохраняет свою цель. И вы решили установить для этой цели значение self.
    Это означает, сохраняется он или нет, но таймер будет поддерживать ваш объект в рабочем состоянии до тех пор, пока таймер действителен.

Имея это в виду, давайте пересмотрим ваш код снизу вверх:

- (void)dealloc {
    [timer_porthole invalidate]; // 1
    [timer_porthole release];
    timer_porthole = nil;  // 2

    [super dealloc];
}

1 не имеет смысла:
Если бы timer_porthole все еще был действительным таймером (то есть запланированным в цикле выполнения), он сохранил бы ваш объект, поэтому этот метод не вызывался бы в первую очередь...

2 здесь тоже нет смысла:
Это dealloc! Когда [super dealloc] вернется, память, которую ваш экземпляр занимал в куче, будет освобождена. Конечно, вы можете обнулить свою часть кучи, прежде чем она будет освобождена. Но зачем беспокоиться?

Тогда есть

-(void) kill_timers{
     [timer_porthole invalidate];
     timer_porthole=nil; // 3
}

3 с учетом вашего инициализатора (и, как указывали другие), вы пропускаете свой таймер здесь; перед этой строкой должен быть [timer_porthole release].


PS:

Если вы все обдумаете, то увидите, что сохранение таймера (по крайней мере временно) создает цикл сохранения. В данном конкретном случае это не проблема, которая решается, как только таймер становится недействительным...

person danyowdee    schedule 03.03.2011

Вы пропустили [timer_porthole release]; вызов в своем kill_timers методе. Если вы вызываете kill_timers до вызова метода dealloc, вы устанавливаете timer_porthole в ноль, но не освобождаете его.

person zrzka    schedule 02.03.2011
comment
Вы уверены, что я должен вызвать релиз в kill_timers? - person Chewie The Chorkie; 02.03.2011
comment
Если вы вызываете keep в initWithFrame, да, вы также должны вызывать release. Если вы не сохраните его, вы не должны вызывать Release. - person zrzka; 02.03.2011
comment
ох... вот в чем моя проблема сейчас. Спасибо! - person Chewie The Chorkie; 02.03.2011
comment
Поэтому я думаю, что в таймерах уничтожения я просто хочу сделать их недействительными, и все. Никакого нуля и, очевидно, никакого релиза. - person Chewie The Chorkie; 02.03.2011