NSTimer как самонаводящийся ивар - PullRequest
5 голосов
/ 14 января 2011

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

// .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, похоже, затруднит использование повторяющегося таймера в качестве ивара, потому что я не могу вспомнить время, когда ивару следует сохранять свой собственный класс.

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

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

Ответы [ 8 ]

4 голосов
/ 14 января 2011

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

@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 голосов
/ 11 апреля 2013

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

THInWeakTimer

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

Может стоит подумать.

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

Имей ивар:

THInWeakTimer *_keepaliveTimer;

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

    __weak FunderwearConnection* wself = self;
    _keepaliveTimer = [[THInWeakTimer alloc] initWithDelay:kIntervalPeriod do:^{
        [wself doSomething];
    }];
2 голосов
/ 14 января 2011

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

1 голос
/ 24 января 2013

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

Сначала мы должны понять, что NSTimer является бесплатным для моста в классе Core Foundation: CFRunLoopTimerRef. И у базовых базовых классов вообще гораздо больше возможностей.

@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 голосов
/ 01 марта 2011

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

0 голосов
/ 01 марта 2011

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

Этот пример кода работает и гарантирует, что retainCount всегда корректен.

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

- (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 голосов
/ 14 января 2011

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

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

Таймер будет повторяться до конца программы.

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

0 голосов
/ 14 января 2011

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

Вы уверены?у вас нет собственности или вызова в классе, что фактически означает, что он собирается умереть?

...