2011-01-14 3 views
5

Я столкнулся с неловкой ситуацией, когда хотел бы иметь класс с переменной экземпляра NSTimer, которая неоднократно вызывает метод класса, пока класс жив. В целях иллюстрации, он может выглядеть следующим образом:NSTimer как самонастраивающийся ivar

// .h 
@interface MyClock : NSObject { 
    NSTimer* _myTimer; 
} 
- (void)timerTick; 
@end 

-

// .m 
@implementation MyClock 

- (id)init { 
    self = [super init]; 
    if (self) { 
     _myTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerTick) userInfo:nil repeats:NO] retain]; 
    } 
    return self; 
} 

- (void)dealloc { 
    [_myTimer invalidate]; 
    [_myTImer release]; 
    [super dealloc]; 
} 

- (void)timerTick { 
    // Do something fantastic. 
} 

@end 

Вот что я хочу. Я не хочу, чтобы выставлять интерфейс в моем классе для запуска и остановки внутреннего таймера, я просто хочу, чтобы он работал во время существования класса. Кажется достаточно простым.

Но проблема в том, что NSTimer сохраняет свою цель. Это означает, что до тех пор, пока этот таймер активен, он не позволяет классу освободиться от обычных методов управления памятью, поскольку таймер сохранил его. Не может быть и речи о ручном регулировании счета удержания. Такое поведение NSTimer, похоже, затрудняет когда-либо повторяющийся таймер как ivar, потому что я не могу думать о времени, когда ivar должен сохранить свой собственный класс.

Это оставляет мне неприятную обязанность придумать какой-то способ предоставления интерфейса в MyClock, который позволяет пользователям класса управлять, когда таймер запускается и останавливается. Помимо добавления ненужной сложности, это раздражает, потому что, если один владелец экземпляра класса делает недействительным таймер, он может наступить на носки другого владельца, который рассчитывает, что он будет продолжать работать. Я мог бы реализовать свою собственную систему псевдо-сохранения-счета для поддержания работы таймера, но ... серьезно? Это способ работать над такой простой концепцией.

Любое решение, которое я могу придумать, кажется хриплым. Я закончил писать обертку для NSTimer, которая ведет себя точно как обычный NSTimer, но не сохраняет свою цель. Мне это не нравится, и я был бы признателен за понимание.

ответ

4

Сохраняющиеся петли, вызванные таймерами, являются болью в шее. Наименее хрупкий подход, который я использовал, - это не сохранить таймер, но всегда установить ссылку на ноль, когда он недействителен.

@interface Foo : NSObject 
{ 
    __weak NSTimer *_timer; 
} 
@end 

@implementation Foo 
- (void) foo 
{ 
    _timer = [NSTimer ....self....]; 
} 

- (void) reset 
{ 
    [_timer invalidate], _timer = nil; 
} 

- (void) dealloc 
{ 
    // since the timer is retaining self, no point in invalidating here because 
    // that just can't happen 
    [super dealloc]; 
} 
@end 

Суть, однако, является то, что вы будете иметь что-то вызов -reset для self должен быть освобожден. Решение состоит в том, чтобы вставить прокси-сервер между self и timer. Прокси-сервер может иметь слабую ссылку на self и просто передает вызов таймера, запускающего до self. Затем, когда self освобождается (поскольку таймер не сохраняет self, а также прокси-сервер), вы можете позвонить invalidate в dealloc.

Или, если вы планируете использовать Mac OS X, включите GC и полностью игнорируйте эту бессмыслицу.

2

Возможно, вы захотите взглянуть на категорию NSTimer от NoodleSoft, которая позволяет вам запускать блок вместо селектора, тем самым избегая удержания вашего класса в качестве цели (а также устраняя необходимость в противном неродственном селекторе в ваш код). Проверьте это here (там также есть куча другого кода, который может вам пригодиться, так что посмотрите, что вы можете найти).

+0

это хорошо, тоже! – bbum

+0

Это выглядит привлекательно. Полагаю, я должен был упомянуть, что я нацелен на iOS здесь, иначе, как упоминалось в bbum, я мог бы просто использовать GC и забыть этот беспорядок. В противном случае отличная ссылка =) –

+0

А, ну. Не видел тег iOS, поэтому не предполагал. Приятно видеть, что вы нашли решение, которое вы можете использовать. –

0

В аналогичной ситуации (на iOS для подкласса UIView) Я перепутал на removeFromSuperview, и в качестве двойной проверки я тестирую свойство .superview каждый раз, когда срабатывает таймер.

Уверены ли вы, что у вас нет собственности или звоните по классу, что фактически означает, что он вот-вот умрет?

+0

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

0

Для повторения таймера, вам не нужно ivar, просто:

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick) userInfo:nil repeats:YES];  

Таймер повторяется до окончания программы.

Для не повторяющегося таймера вы можете проверить: performSelector.

+0

Вам нужен ивар, если вы хотите его аннулировать .... – bbum

+0

У меня есть ivar для его очистки позже. И эта проблема специфична для таймера, который повторяется в течение всего срока жизни класса. –

0

Чтобы справиться с этим, вы можете использовать пользовательский сеттер. Сеттер должен освобождать себя каждый раз, когда цель таймеров - это сам и выпускается каждый раз, когда это было, но больше не работает.

Этот образец кода работает и гарантирует, что keepCount будет правильным в любое время.

Пример пользовательского сеттер

- (void)setMyTimer:(NSTimer *)aNewTimer { // timer retains target/object (self) 
    if(aNewTimer != myTimer) { 


    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

     // since myTimer was retaining its target (self) and aNewTimer is not retaining self (aNewTimer is nil) and will not retain self you – should retain self 
    if(aNewTimer == nil && myTimer != nil) { 
     [self retain]; 
    } 
     // since old timer was not retaining self since myTimer is nil and aNewTimer is retaining self – you should release 
    else if(aNewTimer != nil && myTimer == nil) { 
     [self release]; 
    } 

     [myTimer invalidate]; 
     [myTimer release]; 
     myTimer = [aNewTimer retain]; 

    [pool drain], pool = nil; 

    } 
} 

Помните каждый раз, когда таймер сработает, вы должны установить его в ноль.

- (void)timerDidFire:(NSTimer *)aTimer { 
    if(aTimer == self.myTimer) { 
     self.myTimer = nil; 
    } 
} 

И в dealloc забудьте установить self.myTimer = ноль

0

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

1

Для достижения цели я отправлю собственное решение таймера, который не сохранит цель. Решение с прокси-сервером, вероятно, является самым простым, но тот, который у меня есть, тоже неплохой.

Прежде всего, мы должны понимать, что NSTimer является бесплатным мостом для класса в Core Foundation: CFRunLoopTimerRef. И классы Core Foundation в целом имеют гораздо более широкие возможности.

@implementation Target 

- (void)timerFired:(NSTimer*)timer { 
    ... 
} 

static void timerCallback(CFRunLoopTimerRef timer, void *info) { 
    Target* target = (Target*) info; 

    [target timerFired:(NSTimer*) timer]; 
} 

- (void)startTimer { 
    //timer not retaining its context 

    CFRunLoopTimerContext timerContext; 
    timerContext.version = 0; 
    timerContext.info = self; 
    timerContext.retain = NULL; 
    timerContext.release = NULL; 
    timerContext.copyDescription = NULL; 

    //this is a repeating timer 
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 
                CFAbsoluteTimeGetCurrent() + FIRE_INTERVAL, 
                FIRE_INTERVAL, 
                0, 
                0, 
                timerCallback, 
                &timerContext); 

    //schedule the timer 
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode); 

    self.timer = (NSTimer*) timer; 

    CFRelease(timer); 
} 
@end 

MRC используется здесь, с ARC ему нужны некоторые мостовые отливки.

+0

Обратите внимание, что с помощью Core Foundation вы также можете создавать массивы, словари или наборы, которые не сохраняют свои значения. – Sulthan

2

Я нашел хорошее решение здесь:

THInWeakTimer

https://github.com/th-in-gs/THIn

Может быть стоит рассмотреть.

Используйте это так:

Уже Ивара:

THInWeakTimer *_keepaliveTimer; 

В вашем методе инициализации:

__weak FunderwearConnection* wself = self; 
    _keepaliveTimer = [[THInWeakTimer alloc] initWithDelay:kIntervalPeriod do:^{ 
     [wself doSomething]; 
    }]; 
Смежные вопросы