56

我正在使用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];

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

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

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

4

10 回答 10

77

建议的代码:

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

具有以下效果: (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

然后你会做类似的事情:

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

或者甚至将一个类方法添加到表单的 BTWeakTimerTarget+scheduledTimerWithTimeInterval:target:selector:...以创建该代码的更整洁的表单。你可能想要暴露真实的东西NSTimer以便你可以invalidate这样做,否则建立的规则将是:

  1. 计时器不保留真正的目标;
  2. 在真正的目标开始(并且可能完成)解除分配后,计时器将触发一次,但该触发将被忽略,然后计时器无效。
于 2013-05-29T19:39:44.650 回答
31

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 或更低版本(此时您应该这样做),则无法使用此方法,因此您仍然需要在其他答案中使用代码。

于 2016-09-29T18:10:29.340 回答
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];
    });
}

没有 NSTimer @property,没有无效/运行循环的东西,也没有代理对象,只是一个简单的干净方法。

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

于 2013-11-27T10:19:53.237 回答
9

斯威夫特 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,、、fireisValidfireDate
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.
}
于 2016-12-06T20:11:06.787 回答
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
  }
}

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

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

返回NSTimer的对 有一个弱引用self,因此您可以在 中调用它的invalidate方法deinit

于 2014-12-07T19:53:43.997 回答
3

weakSelf弱没关系,定时器仍然保留对象,所以仍然有一个保留周期。由于运行循环保留了计时器,因此您可以(并且我建议)持有指向计时器的弱指针:

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

关于无效,你的做法是正确的。

于 2013-05-29T19:21:32.820 回答
0

如果您使用的是 Swift,这里有一个自动取消计时器:

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

计时器在 上自动取消deinit

var timer: AutoCancellingTimer? // Strong reference

func startTimer() {
  timer = AutoCancellingTimer(interval: 1, repeats: true) {
    print("Timer fired")
  }
}
于 2015-07-04T05:11:07.640 回答
0

斯威夫特 4 版本。必须在 dealloc 之前调用 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()
    }
}

用法

func  startTimer() {
    timerProxy = TimerProxy(withInterval: 10,
                            repeats: false,
                            timerHandler: { [weak self] in
                                self?.fireTimer()
    })
}

@objc func fireTimer() {
    timerProxy?.invalidate()
    timerProxy = nil
}
于 2018-02-15T12:13:22.473 回答
0

答案很简单。例如你可以试试这个:

@interface Person : NSObject
@property(nonatomic, strong) DemoViewController_19 *vc;
@end

@implementation Person
@end

@interface DemoViewController_19 ()
@property(nonatomic, strong) Person *person;
@end

@implementation DemoViewController_19

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.person = [Person new];
    __weak typeof(self) weaks = self;
    self.person.vc = weaks;
}

@end

运行后可以看到 vc dealloc 没有被调用。这取决于 Person 的强属性属性。

于 2021-06-29T08:44:46.633 回答
0

结合理论和实践。汤米的解决方案是行不通的。

理论上,__weak 实例是作为参数,在实现中

[NSTimer scheduledTimerWithTimeInterval:target:selector: userInfo: repeats:],

目标仍将保留。

您可以实现一个代理,它持有对 self 的弱引用和转发选择器调用,然后将代理作为目标传递。比如YYWeakProxy。

于 2018-11-22T06:40:39.480 回答