对NSTimer目标的弱引用防止保留周期

我正在使用这样的NSTimer

 timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES]; 

当然, NSTimer保留了创build保留周期的目标。 此外, self不是一个UIViewController,所以我没有像viewDidUnload任何东西,我可以使计时器无效打破周期。 所以我想知道如果我可以使用一个弱引用:

 __weak id weakSelf = self; timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES]; 

我听说计时器必须无效(我想从运行循环中释放它)。 但我们可以在我们的dealloc中这样做,对吧?

 - (void) dealloc { [timer invalidate]; } 

这是一个可行的select? 我已经看到很多人处理这个问题的方法,但我没有看到这个。

build议的代码:

 __weak id weakSelf = self; timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES]; 

其效果是:(i)对自我作出弱的指称; (ii)读取弱参考以提供指向NSTimer的指针。 它不会有创build一个弱引用的NSTimer的效果。 该代码和使用__strong引用的唯一区别是,如果在给定的两行之间释放self,则您将传递nil给定时器。

你可以做的最好的事情就是创build一个代理对象。 就像是:

 [...] @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中添加一个类方法,以创build一个整洁的代码forms。 你可能会想要揭露真实的NSTimer这样你就可以使它invalidate ,否则build立的规则将是:

  1. 真正的目标不被计时器保留;
  2. 在真正的目标已经开始(并且可能已经完成)释放之后,定时器将会启动一次,但是这个触发将被忽略并且定时器失效。

如果您不关心计时器事件的毫秒准确性,则可以使用dispatch_after&__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的东西,没有代理对象,只是一个简单的干净的方法。

这种方法的缺点是(与NSTimer不同)块的执行时间(包含[weakSelf doSomethingRepeatedly]; )将影响事件的调度。

iOS 10MacOS 10.12“Sierra”引入了一个新方法+scheduledTimerWithTimeInterval:repeats:block:所以你可以很简单地捕捉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中我定义了一个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... } 

返回的NSTimerself引用很弱,所以你可以在deinit调用它的invalidate方法。

弱者自弱无关 ,定时器仍然保留对象,所以仍然有一个保留周期。 由于定时器是由运行循环保留的,你可以(并且我build议)持有一个指向定时器的弱指针:

 NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:@selector(tick) userInfo:nil repeats:YES]; 

关于无效你做的方式是正确的。

Swift 3

应用目标<iOS 10

自定义WeakTimer( GitHubGist )实现:

 final class WeakTimer { fileprivate weak var timer: Timer? fileprivate weak var target: AnyObject? fileprivate let action: (Timer) -> Void fileprivate init(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) { self.target = target self.action = action self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(fire), userInfo: nil, repeats: repeats) } class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, repeats: Bool, action: @escaping (Timer) -> Void) -> Timer { return WeakTimer(timeInterval: timeInterval, target: target, repeats: repeats, action: action).timer! } @objc fileprivate func fire(timer: Timer) { if target != nil { action(timer) } else { timer.invalidate() } } } 

用法:

 let timer = WeakTimer.scheduledTimer(timeInterval: 2, target: self, repeats: true) { [weak self] timer in // Place your action code here. } 

timer是标准类Timer实例,所以你可以使用所有可用的方法(例如, invalidatefireisValidfireDate等)。
timer实例将在释放self或定时器的作业完成时解除分配(例如repeats == false )。

应用目标> = iOS 10
标准计时器实现:

 open class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Swift.Void) -> Timer 

用法:

 let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in // Place your action code here. } 

如果你正在使用Swift,这里是一个自动取消计时器:

https://gist.github.com/evgenyneu/516f7dcdb5f2f73d7923

定时器在取消时自动取消。

 var timer: AutoCancellingTimer? // Strong reference func startTimer() { timer = AutoCancellingTimer(interval: 1, repeats: true) { print("Timer fired") } }