6

これが私の問題です:

NSTimerモデルオブジェクトの存続期間全体にわたってタイマーを実行するモデルクラスがあります。初期化は簡単です。initメソッドに次のコード行が含まれているだけです。

self.maintainConnectionTimer = 
             [NSTimer scheduledTimerWithTimeInterval:1 
                                              target:self 
                                            selector:@selector(maintainConnection) 
                                            userInfo:nil 
                                             repeats:YES];

ただし、私の問題は、モデルがメモリから解放されたときにこのタイマーを無効にするにはどうすればよいですか? さて、これは通常簡単ですが、私が知る限り、OSをスケジュールするとNSTimer、OSはTimerオブジェクトへの強力なポインターを維持します。

これにどう対処すればいいですか?モデルがメモリから解放される直前に呼び出されるメソッドはありますか?

4

2 に答える 2

22

[NSTimer scheduledTimerWithTimeInterval:...] ターゲットを保持するため、ターゲットがselfの場合、モデルクラスのインスタンスの割り当てが解除されることはありません。

回避策として、別のオブジェクト(TimerTarget次の例で呼び出されます)を使用できます。保持サイクルを回避するために、へTimerTarget参照があります。ModelClass

この「ヘルパークラス」はこんな感じ。その唯一の目的は、タイマーイベントを「実際のターゲット」に転送することです。

@interface TimerTarget : NSObject
@property(weak, nonatomic) id realTarget;
@end

@implementation TimerTarget

- (void)timerFired:(NSTimer*)theTimer
{
    [self.realTarget performSelector:@selector(timerFired:) withObject:theTimer];
}

@end

これで、モデルクラスで、タイマーを作成して無効にすることができますdealloc

@interface ModelClass ()
@property(strong, nonatomic) NSTimer *timer;
@end

@implementation ModelClass

- (id)init
{
    self = [super init];
    if (self) {
        TimerTarget *timerTarget = [[TimerTarget alloc] init];
        timerTarget.realTarget = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                 target:timerTarget
                                               selector:@selector(timerFired:)
                                               userInfo:nil repeats:YES];
    }
    return self;
}

- (void)dealloc
{
    [self.timer invalidate]; // This releases the TimerTarget as well!
    NSLog(@"ModelClass dealloc");
}

- (void)timerFired:(NSTimer*)theTimer
{
    NSLog(@"Timer fired");
}

@end

だから私たちは

modelInstance ===> timer ===> timerTarget ---> modelInstance
(===> : strong reference, ---> : weak reference)

タイマーからモデルクラスのインスタンスへの(強力な)参照はもうないことに注意してください。

次のコードでこれをテストしました。このコードは、のインスタンスを作成し、ModelClass5秒後にリリースします。

__block ModelClass *modelInstance = [[ModelClass alloc] init];
int64_t delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    modelInstance = nil;
});

出力:

2013-01-23 23:54:11.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:12.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:13.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:14.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:15.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:15.484 timertest[16576:c07] ModelClass dealloc
于 2013-01-23T22:54:31.833 に答える
0

@Martin Rのアイデアに基づいて、使いやすいカスタムクラスを作成し、クラッシュを回避するためのチェックを追加します。

@interface EATimerTarget : NSObject

// Initialize with block to avoid missing call back
- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void(^)(NSTimer *))block;

// For NSTimer @selector() parameter
- (void)timerFired:(NSTimer *)timer;

@end

@interface EATimerTarget ()
@property (weak, nonatomic) id realTarget;
@property (nonatomic, copy) void (^timerBlock)(NSTimer *); // use 'copy' to avoid retain counting
@end

@implementation EATimerTarget

- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void (^)(NSTimer *))block {
    self = [super init];
    if (self) {
        self.realTarget = realTarget;
        self.timerBlock = block;
    }
    return self;
}

- (void)timerFired:(NSTimer *)timer {
    // Avoid memory leak, timer still run while our real target is dealloc
    if (self.realTarget) {
        self.timerBlock(timer);
    }
    else {
        [timer invalidate];
    }
}

@end

これが私のサンプルクラスです

@interface MyClass
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation MyClass

- (id)init
{
    self = [super init];
    if (self) {
         // Using __weak for avoiding retain cycles
         __weak typeof(self) wSelf = self;
    EATimerTarget *timerTarget = [[EATimerTarget alloc] initWithRealTarget:self timerBlock: ^(NSTimer *timer) {
        [wSelf onTimerTick:timer];
    }];
         self.timer = [NSTimer timerWithTimeInterval:1 target:timerTarget selector:@selector(timerFired:) userInfo:nil repeats:YES];
         [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    }
    return self;
}

- (void)dealloc
{
    [self.timer invalidate]; // This releases the EATimerTarget as well! 
    NSLog(@"### MyClass dealloc");
}

- (void)onTimerTick:(NSTimer *)timer {
     // DO YOUR STUFF!
     NSLog(@"### TIMER TICK");
}

@end
于 2015-07-25T07:22:37.607 に答える