NSTimer 作为自我定位 ivar
我遇到过一个尴尬的情况,我希望有一个带有 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 的这种行为似乎会让重复计时器变得很难作为 ivar,因为我想不出 ivar 应该保留其所属类的时间。
这让我承担了一项令人不快的任务,那就是想出一些在 MyClock 上提供接口的方法,允许该类的用户控制计时器何时启动和停止。除了增加不必要的复杂性之外,这很烦人,因为让该类实例的一个所有者使计时器无效可能会踩到另一个指望计时器继续运行的所有者的脚趾。我可以实现我自己的伪保留计数系统来保持计时器运行,但是……认真的吗?对于这样一个简单的概念来说,这是需要做很多工作的。
我能想到的任何解决方案都感觉很糟糕。我最终为 NSTimer 编写了一个包装器,其行为与普通 NSTimer 完全相同,但不保留其目标。我不喜欢它,我会很感激任何见解。
I have come across an awkward situation where I would like to have a class with an NSTimer instance variable that repeatedly calls a method of the class as long as the class is alive. For illustration purposes, it might look like this:
// .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
That's what I want. I don't want to to have to expose an interface on my class to start and stop the internal timer, I just want it to run while the class exists. Seems simple enough.
But the problem is that NSTimer retains its target. That means that as long as that timer is active, it is keeping the class from being dealloc'd by normal memory management methods because the timer has retained it. Manually adjusting the retain count is out of the question. This behavior of NSTimer seems like it would make it difficult to ever have a repeating timer as an ivar, because I can't think of a time when an ivar should retain its owning class.
This leaves me with the unpleasant duty of coming up with some method of providing an interface on MyClock that allows users of the class to control when the timer is started and stopped. Besides adding unneeded complexity, this is annoying because having one owner of an instance of the class invalidate the timer could step on the toes of another owner who is counting on it to keep running. I could implement my own pseudo-retain-count-system for keeping the timer running but, ...seriously? This is way to much work for such a simple concept.
Any solution I can think of feels hacky. I ended up writing a wrapper for NSTimer that behaves exactly like a normal NSTimer, but doesn't retain its target. I don't like it, and I would appreciate any insight.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
由定时器引起的保留循环是一个令人头疼的问题。我使用过的最不脆弱的方法是不保留计时器,但在使其无效时始终将引用设置为nil。
但最重要的是,您必须调用
-reset
才能释放self
。解决方案是在self
和timer
之间放置一个代理。代理可以具有对 self 的弱引用,并且只需将计时器触发的调用传递给 self 。然后,当self
被释放时(因为计时器不保留self
,代理也不保留),您可以在中调用
。invalidate
>dealloc或者,如果针对 Mac OS X,请打开 GC 并完全忽略这些废话。
The retain loops caused by timers are a pain in the neck. The least fragile approach I've used is to not retain the timer, but always set the reference to nil when invalidating it.
The bottom line, though, is that you'll have to have something call
-reset
forself
to be released. A solution is to stick a proxy betweenself
and thetimer
. The proxy can have a weak reference toself
and simply passes the invocation of the timer firing along toself
. Then, whenself
is deallocated (since the timer doesn't retainself
and neither does the proxy), you can callinvalidate
indealloc
.Or, if targeting Mac OS X, turn on GC and ignore this nonsense entirely.
您可能想看看 NoodleSoft 的 NSTimer 类别,它允许您让计时器运行一个块而不是一个选择器,从而避免将您的类保留为目标(并且还消除了对令人讨厌的代码中不相关的选择器)。请查看此处(其中还有一些您可能会发现有用的其他代码,所以请看一下周围看看你能找到什么)..
You might want to take a look at NoodleSoft's
NSTimer
category that allows you to have the timer run a block instead of a selector, thereby avoiding retention of your class as target (and also eliminating the need for a pesky unrelated selector in your code). Check it out here (there's also a bunch of other code you might find useful in there, so take a look around and see what you can find)..为了完整起见,我将发布我自己的不保留其目标的计时器解决方案。使用代理的解决方案可能是最简单的,但我拥有的解决方案也不错。
首先,我们必须了解 NSTimer 是免费桥接到 Core Foundation 中的类:CFRunLoopTimerRef 的。一般来说,核心基础课程有更大的可能性。
这里使用 MRC,使用 ARC 需要一些桥接转换。
For the sake of completness, I will post my own solution of a timer which doesn't retain its target. The solution with a proxy is probably the simplest but the one I have is not bad either.
First we have to understand that
NSTimer
is toll free bridged to a class in Core Foundation:CFRunLoopTimerRef
. And Core Foundation classes in general have much bigger possibilites.MRC is used here, with ARC it needs some bridging casts.
我在这里找到了一个很好的解决方案:
THInWeakTimer
https://github.com/th-in-gs/THIn
可能值得考虑。
像这样使用它:
有一个 ivar:
在你的 init 方法中:
I found a nice solution here:
THInWeakTimer
https://github.com/th-in-gs/THIn
Might be worth considering.
Use it like this:
Have an ivar:
In your init method:
在类似的情况下(在 iOS 上,对于 UIView 子类),我搭载了
removeFromSuperview
,并且作为双重检查,我在每次计时器触发时测试.superview
属性。您确定您没有任何财产或对班级的调用,这实际上意味着它即将消亡吗?
In a similar situation (on iOS, for a UIView subclass) I piggybacked on
removeFromSuperview
, and as a double check I test the.superview
property every time the timer fires.Are you sure you don't have any property or call on the class which effectively means that it's about to die?
对于重复计时器,您不需要
ivar
,只需:计时器将重复直到程序结束。
对于非重复计时器,您可以查看:
performSelector
。For repeating timer, you do not need the
ivar
, just:The timer will repeats till end of the program.
For a non-repeating timer, you may check out:
performSelector
.您可以使用自定义设置器来处理此问题。每次计时器目标是 self 时,setter 都应释放 self ,并在每次它是但不再是时释放 self 。
此代码示例正在运行,并确保保留计数始终正确。
自定义设置器的示例
请记住,每次计时器触发时,您都应该将其设置为零。
在 dealloc 中记得设置 self.myTimer = nil
You could use an custom setter to handle this. The setter should release self every time the timers target is self and release everytime it was but is no longer.
This sample of code is working and ensures that the retainCount is correct at all times.
Example of custom setter
Remember everytime the timer fires you should set it to nil.
And in dealloc remember to set self.myTimer = nil
您还可以使用不保留自身但分配的代理对象..会很干净。
You could also use an proxy-object which does not retain self but assigns.. would be clean.