Objective c 对NSTimer目标的弱引用可防止保留周期
我使用的是Objective c 对NSTimer目标的弱引用可防止保留周期,objective-c,automatic-ref-counting,nstimer,retain-cycle,Objective C,Automatic Ref Counting,Nstimer,Retain Cycle,我使用的是NSTimer如下: timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES]; 当然,NSTimer会保留创建保留周期的目标。此外,self不是UIViewController,因此我没有类似于viewDidUnload的东西,可以使计时器失效以中断循环。所以我想知道我是否可以使用弱引用: __weak id
NSTimer
如下:
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES];
当然,NSTimer
会保留创建保留周期的目标。此外,self
不是UIViewController,因此我没有类似于viewDidUnload
的东西,可以使计时器失效以中断循环。所以我想知道我是否可以使用弱引用:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];
我听说计时器必须失效(我想应该从运行循环中释放它)。但我们可以在我们的交易中这样做,对吗
- (void) dealloc {
[timer invalidate];
}
这是一个可行的选择吗?我见过很多人处理这个问题的方法,但我没有见过。不管weakSelf是否弱,计时器仍然保留对象,因此仍然有一个保留周期。由于运行循环保留计时器,因此可以(我建议)保留指向计时器的弱指针:
NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:@selector(tick) userInfo:nil repeats:YES];
“关于使无效”您的做法是正确的。建议的代码:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];
具有以下效果:(i)对自我的引用较弱;(ii)读取该弱引用是为了提供指向NSTimer
的指针。它不会产生使用弱引用创建NSTimer
的效果。该代码与使用\u strong
引用之间的唯一区别在于,如果在给定的两行之间取消分配self,则将nil
传递给计时器
您可以做的最好的事情是创建一个代理对象。比如:
[...]
@implementation BTWeakTimerTarget
{
__weak target;
SEL selector;
}
[...]
- (void)timerDidFire:(NSTimer *)timer
{
if(target)
{
[target performSelector:selector withObject:timer];
}
else
{
[timer invalidate];
}
}
@end
BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
然后你会这样做:
[...]
@implementation BTWeakTimerTarget
{
__weak target;
SEL selector;
}
[...]
- (void)timerDidFire:(NSTimer *)timer
{
if(target)
{
[target performSelector:selector withObject:timer];
}
else
{
[timer invalidate];
}
}
@end
BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
或者甚至向形式为+scheduledTimerWithTimeInterval:target:selector:…
的BTWeakTimerTarget添加一个类方法,以创建该代码的更整洁的形式。您可能希望公开真实的NSTimer
,以便使其无效,否则将建立以下规则:
定时器没有保留真正的目标李>
在实际目标开始(可能已经完成)释放后,计时器将触发一次,但该触发将被忽略,计时器将失效
如果您不太关心计时器事件的毫秒精度,那么可以使用dispatch\u after&\uu-weak而不是NSTimer来执行此操作。以下是代码模式:
- (void) doSomethingRepeatedly
{
// Do it once
NSLog(@"doing something …");
// Repeat it in 2.0 seconds
__weak typeof(self) weakSelf = self;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf doSomethingRepeatedly];
});
}
没有NSTimer@property,没有invalidate/runloop内容,也没有代理对象,只是一个简单的clean方法
这种方法的缺点是(与NSTimer
)块的执行时间(包含[weakSelf dosomethingreepeatedly];
)将影响事件的调度。在Swift中,我定义了一个WeakTimer
助手类:
/// A factory for NSTimer instances that invoke closures, thereby allowing a weak reference to its context.
struct WeakTimerFactory {
class WeakTimer: NSObject {
private var timer: NSTimer!
private let callback: () -> Void
private init(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) {
self.callback = callback
super.init()
self.timer = NSTimer(timeInterval: timeInterval, target: self, selector: "invokeCallback", userInfo: userInfo, repeats: repeats)
}
func invokeCallback() {
callback()
}
}
/// Returns a new timer that has not yet executed, and is not scheduled for execution.
static func timerWithTimeInterval(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) -> NSTimer {
return WeakTimer(timeInterval: timeInterval, userInfo: userInfo, repeats: repeats, callback: callback).timer
}
}
然后你可以像这样使用它:
let timer = WeakTimerFactory.timerWithTimeInterval(interval, userInfo: userInfo, repeats: repeats) { [weak self] in
// Your code here...
}
返回的NSTimer
对self
的引用较弱,因此如果使用Swift,可以在deinit中调用其invalidate
方法这里有一个自动取消计时器:
var timer: AutoCancellingTimer? // Strong reference
func startTimer() {
timer = AutoCancellingTimer(interval: 1, repeats: true) {
print("Timer fired")
}
}
定时器会在deinit
上自动取消自身
var timer: AutoCancellingTimer? // Strong reference
func startTimer() {
timer = AutoCancellingTimer(interval: 1, repeats: true) {
print("Timer fired")
}
}
iOS 10和macOS 10.12“Sierra”引入了一种新方法,因此您可以捕获self
如下:
__weak MyClass* weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) {
MyClass* _Nullable strongSelf = weakSelf;
[strongSelf doSomething];
}];
Swift 3中的等效性:
_timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
self?.doSomething()
}
如果您仍然需要以iOS 9或更低版本为目标(此时您应该这样做),则无法使用此方法,因此您仍然需要在其他答案中使用代码。Swift 3
应用程序目标:
自定义WeakTimer()实现:
用法:
timer
是标准类timer
的实例,因此您可以使用所有可用的方法(例如invalidate
、fire
、isValid
、fireDate
等)。
timer
实例将在self
解除分配或计时器的作业完成时解除分配(例如,repeats==false
)
应用程序目标>=iOS 10:
标准计时器实现:
用法:
Swift 4版本。必须在解除锁定之前调用Invalidate
class TimerProxy {
var timer: Timer!
var timerHandler: (() -> Void)?
init(withInterval interval: TimeInterval, repeats: Bool, timerHandler: (() -> Void)?) {
self.timerHandler = timerHandler
timer = Timer.scheduledTimer(timeInterval: interval,
target: self,
selector: #selector(timerDidFire(_:)),
userInfo: nil,
repeats: repeats)
}
@objc func timerDidFire(_ timer: Timer) {
timerHandler?()
}
func invalidate() {
timer.invalidate()
}
}
用法
理论和实践的结合。汤米的解决方案是行不通的
理论上,弱实例作为参数,在实现
[NSTimer scheduledTimerWithTimeInterval:target:selector: userInfo: repeats:],
目标仍将保留
您可以实现一个代理,它保存弱引用并将选择器调用转发给self,然后将代理作为目标传递。例如YYWeakProxy 但是runloop不会保留对计时器的强引用吗?计时器会保留对self
的强引用,并且dealloc
不会发生?至少它避免了保留循环。我仍然同意使用弱定时器更好。@Tommy是正确的,它不能避免保留周期。只要计时器没有失效,对象就永远不会死,如果只有dealloc使计时器失效,它也永远不会失效。弱计时器是毫无意义的,几乎从来都不是你想要的。计时器也可以调用弱选择器。@frangulyan不,它没有。它说“因为运行循环维护计时器”,这意味着运行循环使计时器保持活动状态。不管你是否让它活着,只要它被安排好,它就不会死,而取消计时器计划的官方方法是使它失效。它还表示“计时器保持对其目标的强引用”。因此,只要计时器处于活动状态,它也将保持其目标处于活动状态。所以,让计时器变弱不会修复任何东西,除非你在某个时候使它失效。太棒了,谢谢。最后,我制作了一个NSWeakTimer
shell,它处理了bendytree的选择器接线。我查看了您的代码。目标不应是分配
属性。相反,它应该是@Tommy描述的弱属性。取消分配后,分配属性不会变为nil
,而弱属性会变为。因此,您的if(target)
检查将永远不会成为真的。@Pwner我检查了它并将赋值更改为弱,但它仍然不会使它无效。我该怎么办?@sftsz您是在使用runloop运行吗
func startTimer() {
timerProxy = TimerProxy(withInterval: 10,
repeats: false,
timerHandler: { [weak self] in
self?.fireTimer()
})
}
@objc func fireTimer() {
timerProxy?.invalidate()
timerProxy = nil
}
[NSTimer scheduledTimerWithTimeInterval:target:selector: userInfo: repeats:],