对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立的规则将是:
- 真正的目标不被计时器保留;
- 在真正的目标已经开始(并且可能已经完成)释放之后,定时器将会启动一次,但是这个触发将被忽略并且定时器失效。
如果您不关心计时器事件的毫秒准确性,则可以使用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 10和MacOS 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... }
返回的NSTimer
对self
引用很弱,所以你可以在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
实例,所以你可以使用所有可用的方法(例如, invalidate
, fire
, isValid
, fireDate
等)。
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") } }