对NSTimer目标的弱参考以防止保留周期

ben*_*ree 52 objective-c nstimer automatic-ref-counting retain-cycle

我用的NSTimer是这样的:

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

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

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

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

- (void) dealloc {
    [timer invalidate];
}
Run Code Online (Sandbox Code Playgroud)

这是一个可行的选择吗?我已经看到人们处理这个问题的方法很多,但我还没有看到这个.

Tom*_*mmy 74

建议的代码:

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

具有以下效果:(i)对自我的弱引用; (ii)读取弱引用以提供指针NSTimer.它不具有NSTimer使用弱引用创建的效果.该代码与使用__strong引用之间的唯一区别是,如果在给定的两行之间释放self,那么您将传递nil给计时器.

您可以做的最好的事情是创建一个代理对象.就像是:

[...]
@implementation BTWeakTimerTarget
{
    __weak target;
    SEL selector;
}

[...]

- (void)timerDidFire:(NSTimer *)timer
{
    if(target)
    {
        [target performSelector:selector withObject:timer];
    }
    else
    {
        [timer invalidate];
    }
}
@end
Run Code Online (Sandbox Code Playgroud)

然后你会做类似的事情:

BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
Run Code Online (Sandbox Code Playgroud)

或者甚至将类方法添加到表单的BTWeakTimerTarget +scheduledTimerWithTimeInterval:target:selector:...以创建该代码的更简洁的形式.您可能希望公开真实,NSTimer以便您可以invalidate,否则建立的规则将是:

  1. 计时器不保留真实目标;
  2. 在真实目标开始(并且可能已完成)解除分配后,计时器将触发一次,但是该触发将被忽略,然后计时器无效.

  • 太好了,谢谢.我最终制作了一个处理选择器接线的`NSWeakTimer` shell https://gist.github.com/bendytree/5674709 (3认同)

Jay*_*hao 26

如果您不关心计时器事件的毫秒准确性,可以使用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];
    });
}
Run Code Online (Sandbox Code Playgroud)

没有NSTimer @property,没有无效/ runloop的东西,没有代理对象,只是一个简单的干净方法.

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


ken*_*ytm 25

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];
}];
Run Code Online (Sandbox Code Playgroud)

Swift 3中的等价:

_timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
    self?.doSomething()
}
Run Code Online (Sandbox Code Playgroud)

如果您仍然需要针对iOS 9或更低版本(此时您应该使用此目标),则无法使用此方法,因此您仍需要在其他答案中使用代码.


Vla*_*pko 8

斯威夫特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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

timer是标准的类的实例Timer,所以你可以使用所有可用的方法(例如invalidate,fire,isValid,fireDate和等).
timerself取消分配或计时器的工作完成时(例如repeats == false),将释放实例.

App target> = iOS 10:
标准计时器实现:

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

用法:

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

  • @mkirk,是的,但是`Timer`是唯一一个强烈引用'WeakTimer`的人.这意味着只要`Timer`存在,`WeakTimer`实例就会存在.`Timer`一直存在,直到调用`fileprivate func fire`并且`target`已经死亡.`target`有它自己的生命,上面的桶里没有人强烈提到`target`. (2认同)

sha*_*ter 5

在 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
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

let timer = WeakTimerFactory.timerWithTimeInterval(interval, userInfo: userInfo, repeats: repeats) { [weak self] in
  // Your code here...
}
Run Code Online (Sandbox Code Playgroud)

返回的值NSTimer对 具有弱引用self,因此您可以invalidate在 中调用其方法deinit