这是我的问题:
我有一个模型类,其中包含NSTimer
,我希望Timer在模型对象的整个生命周期内运行。初始化很简单:我在init
方法中只有以下代码行:
self.maintainConnectionTimer =
[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(maintainConnection)
userInfo:nil
repeats:YES];
但是,我的问题是,当模型从内存中释放时,如何使此计时器无效?现在,这通常很容易,但据我所知,当您安排时NSTimer
操作系统维护一个指向Timer对象的强指针。
我该如何处理?是否有一种方法在模型从内存中释放之前被调用?
答案 0 :(得分: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)
请注意,定时器没有(强)引用到模型类的实例。
我使用以下代码对此进行了测试,该代码创建ModelClass
的实例并在5秒后释放它:
__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
答案 1 :(得分: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