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

时间:2013-05-29 18:54:30

标签: 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 weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];

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

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

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

10 个答案:

答案 0 :(得分:74)

建议的代码:

__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:) ...];

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

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

答案 1 :(得分: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,没有无效/ runloop的东西,没有代理对象,只是一个简单的干净方法。

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

答案 2 :(得分:22)

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

答案 3 :(得分:8)

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的实例,因此您可以使用所有可用的方法(例如invalidatefireisValid,{{1}等等) 当fireDate被取消分配或计时器的工作完成时(例如timer),self实例将被取消分配。

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

repeats == false

用法:

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

答案 4 :(得分:4)

在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的引用较弱,因此您可以在invalidate中调用其deinit方法。

答案 5 :(得分:3)

weakSelf 弱的情况并不重要,计时器仍保留对象,因此仍然存在保留周期。由于运行循环保留了一个计时器,你可以(我建议)保持一个指向计时器的弱指针:

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

关于无效您正在做的事情是正确的。

答案 6 :(得分: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")
  }
}

答案 7 :(得分:0)

Swift 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
}

答案 8 :(得分:0)

有理论和实践经验。汤米的解决方案行不通。

从理论上讲,__ weak实例作为参数,在

的实现中
"""Rock, Paper, Scissors Exercise 8"""
game= input("Are you ready to ply? Y or N: ").capitalize()
user1 = input("What's your name? ")
user2 = input("What's your name? ")
p1 = input(user1 + ": Rock, Paper, Scissors? ").lower()
p2 = input(user2 + ": Rock, Paper, Scissors? ").lower()
p1_count=0
p2_count=0
games_played = 0

while game == "Y":
    if p1 == "rock":
        if p2 == "rock":
            print("It\'s a tie!")
            game = input("Are you ready to ply? Y or N: ").capitalize()
            p1_count += 1
            p2_count += 1
            games_played += 1
        elif p2 == "scissors":
            print(user2 + ", you got beat mothafucka!")
            game = input("Are you ready to play? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1
        elif p2 == "paper":
            print(user1 + ", you got beat mothafucka!")
            game = input("Are you ready to play? Y or N: ").capitalize()
            p2_count += 1
            games_played += 1
    elif p1 == "scissors":
        if p2 == "scissors":
            print("It\'s a tie!")
            game = input("Are you ready to play? Y or N: ").capitalize()
            p1_count += 1
            p2_count += 1
            games_played += 1
        elif p2 == "paper":
            print(user2 + ", you got beat mothafucka!")
            game = input("Are you ready to play? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1
        elif p2 == "rock":
            print(user1 + ", you got beat mothafucka!")
            game = input("Are you ready to play? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1
    elif p1 == "paper":
        if p2 == "paper":
            print("It\'s a tie!")
            game = input("Are you ready to ply? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1
        elif p2 == "rock":
            print(user2 + ", you got beat mothafucka!")
            game = input("Are you ready to ply? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1
        elif p2 == "scissors":
            print(user1 + ", you got beat mothafucka!")
            game = input("Are you ready to ply? Y or N: ").capitalize()
            p1_count += 1
            games_played += 1


print("Thank you " + user1 + " and " + user2 + " for playing this classic fucking 
game!")
print("With " + str(games_played) + " games played, " + "the score was " + user1 + " 
with " + str(p1_count) + " and " + user2 + " with " + str(p2_count))

目标将保持不变。

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

答案 9 :(得分: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 的强属性属性。