Estoy usando un NSTimer como este:

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

De curso, NSTimer conserva el destino que crea una retener ciclo. Además, self no es un UIViewController, así que no tiene nada de viewDidUnload donde puedo anular el temporizador para romper el ciclo. Entonces, me pregunto si puedo usar una referencia débil en su lugar:

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

He oído que el temporizador debe ser invalidado (supongo que para liberarlo de la ejecución del bucle). Pero que podemos hacer, que en nuestro dealloc, ¿verdad?

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

Es esta una opción viable? He visto un montón de maneras en que las personas a lidiar con este problema, pero yo no he visto esto.

  • Más allá de las respuestas que figuran a continuación, que nadie explicó por qué invalidar el temporizador en dealloc es inútil (de aquí): Un temporizador mantiene una fuerte referencia a su objetivo. Esto significa que mientras el temporizador sigue siendo válida, su objetivo no será desasignado. Como corolario, esto significa que no tiene sentido para un temporizador de destino para intentar anular el temporizador en su dealloc método—el método dealloc no se invocará mientras el temporizador es válido.
InformationsquelleAutor bendytree | 2013-05-29

9 Comentarios

  1. 74

    La propuesta de código:

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

    tiene el efecto de que (i) una débil se hace referencia al mismo; (ii) que la debilidad de la referencia se lee en el fin de proporcionar un puntero a NSTimer. No va a tener el efecto de crear un NSTimer con una referencia débil. La única diferencia entre el código y el uso de un __strong referencia es que si el auto se cancela la asignación entre las dos líneas dadas a continuación, usted va a pasar nil para el temporizador.

    La mejor cosa que puedes hacer es crear un objeto proxy. Algo así como:

    [...]
    @implementation BTWeakTimerTarget
    {
        __weak target;
        SEL selector;
    }
    
    [...]
    
    - (void)timerDidFire:(NSTimer *)timer
    {
        if(target)
        {
            [target performSelector:selector withObject:timer];
        }
        else
        {
            [timer invalidate];
        }
    }
    @end

    Entonces usted haría algo así:

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

    O incluso agregar un método de clase a BTWeakTimerTarget de la forma +scheduledTimerWithTimeInterval:target:selector:... para crear una prolija forma de ese código. Usted probablemente querrá exponer el real NSTimer de modo que usted puede invalidate ella, de lo contrario las normas establecidas serán:

    1. que el verdadero objetivo no es retenido por el temporizador,
    2. el temporizador se activará una vez después de que el objetivo real ha comenzado (y probablemente completado) desasignación, pero que el despido será ignorado y el temporizador invalidado entonces.
    • Impresionante, gracias. Terminé haciendo un NSWeakTimer shell que maneja el selector de cableado gist.github.com/bendytree/5674709
    • He revisado el código. El objetivo no debe ser un assign de la propiedad. Más bien, debe ser un weak propiedad como @Tommy descrito. Asignar propiedades no se conviertan en nil después de la cancelación de la asignación, mientras que los débiles propiedades de hacer. Por lo tanto su if (target) de verificación nunca se convertirá en verdad.
    • Impresionante solución.
    • Lo he comprobado y cambiado asignar a la débil pero nunca la invalidan. ¿qué debo hacer?
    • Utilizando un runloop para ejecutar el temporizador?
    • Sólo traté de @bendytree solución y he estado rascando mi cabeza para siempre hasta que me encontré con que no se le está pasando un objeto a su selector en el «fuego» del método. Mi aplicación utiliza la repetición de los temporizadores y invalida en el selector llamado por «fuego» si se cumple una cierta condición. Como no hay ningún objeto fue aprobada, el temporizador nunca fue invalidado y corrió para siempre. Quien es copiar/pegar el código debe asegurarse de que el uso de «withObject:temporizador» en lugar de «withObject:nil». De todos modos, gracias por el fragmento de código!!

  2. 26

    Si usted no se preocupa por la precisión de milisegundos de los eventos del temporizador, usted podría utilizar dispatch_after & __débiles en lugar de NSTimer para ello. Aquí está el código de patrón:

    - (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];
        });
    }

    No NSTimer @de la propiedad, no invalida/runloop cosas y no objeto de proxy, sólo un simple método.

    La desventaja de este enfoque es que (a diferencia de NSTimer) el tiempo de ejecución del bloque (que contiene [weakSelf doSomethingRepeatedly];) tendrán un impacto en la programación de los eventos.

    • Gran idea. Y si quieres que se repita, puede verificar si weakSelf != nil y si es así, simplemente llama recursivamente a dispatch_after en el interior del despacho de bloque. El único inconveniente es que hace que sea difícil para alterar el temporizador (por ejemplo, para cambiar el intervalo) después de que se ha configurado.
    • Tenga cuidado al aplicar una repetición de temporizador utilizando este método. Cuando se utiliza un regular repitiendo NSTimer, la ejecución de la duración de la meta selector de no afectar a su tiempo. En este caso, si usted no hace otra dispatch_after inmediatamente cuando el temporizador se llama (y aún así) tendrá temporización sesga.
  3. 23

    iOS 10 y macOS 10.12 «Sierra» introdujo un nuevo método, +scheduledTimerWithTimeInterval:repeticiones:bloque:, por lo que se podría capturar self débilmente simplemente como:

    __weak MyClass* weakSelf = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) {
        MyClass* _Nullable strongSelf = weakSelf;
        [strongSelf doSomething];
    }];

    Equivalencia en Swift 3:

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

    Si usted todavía necesita de destino iOS 9 o por debajo (lo que usted debe en este momento), este método no puede ser utilizado, de modo que usted todavía tiene que usar el código en otras respuestas.

    • Esto doesnt trabajo hasta que el temporizador se invalida incluso si el auto es débil
    • entonces se podría hacer algo como esto o no? guard let weakSelf = self else { timer.invalidate(); return }. El temporizador va a ser invalidado, cuando el auto no está disponible.
  4. 8

    Swift 3

    De la aplicación de destino < iOS 10:

    Personalizado WeakTimer (GitHubGist) aplicación:

    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()
            }
        }
    }

    Uso:

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

    timer es instancia de la clase estándar Timer, así que usted puede utilizar todos los métodos disponibles (por ejemplo,invalidate, fire, isValid, fireDate y etc).

    timer instancia serán removidos cuando self se cancela la asignación o cuando el temporizador del trabajo está hecho (por ejemplo,repeats == false).

    De la aplicación de destino >= iOS 10:

    Temporizador estándar de ejecución:

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

    Uso:

    let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
        //Place your action code here.
    }
    • Pero no sólo «pasar la pelota» a WeakTimer, desde WeakTimer#init ahora pasa Timer una fuerte referencia a target: self?
    • Sí, pero Timer es la única persona que mantiene una fuerte referencia a WeakTimer. Eso significa que WeakTimer instancia de la vida mientras Timer vidas. Timer vidas hasta el momento cuando fileprivate func fire se llama y target ya murió. target tiene su propia vida, nadie por encima de la cubeta mantiene fuerte referencia a target.
  5. 4

    En Swift he definido un WeakTimer clase auxiliar:

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

    Y, a continuación, se puede utilizar como tal:

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

    La devuelve NSTimer tiene una débil referencia a self, así que usted puede llamar a su invalidate método en deinit.

    • Usted quiere pasar userInfo en el NSTimer constructor, no?
    • Doh, gran captura! He actualizado el código en mi respuesta. (Así como el código de mi propia repo… yo siempre había estado pasando en nil para userInfo, así que estoy totalmente perdido esto.) Gracias!
    • Gracias. Esto funciona en mi Swift proyecto!
    • No entiendo cómo uno se supone que debe ejecutar el temporizador, desde su bloque de código no ejecutarlo
    • esto conduce a la Pérdida de los Cheques de problemas (perfil en los Instrumentos). pruebe este gist.github.com/onevcat/2d1ceff1c657591eebde
    • el weakTimer regresó de método estático no conservar a sí mismo si no se repita. Así que sólo funcionan en el modo de repetición. Y, esto se debe añadir runloop manualmente.

  6. 3

    No importa que weakSelf es débil, el temporizador aún conserva el objeto de modo que todavía hay una retener ciclo. Desde un temporizador es retenida por la ejecución del bucle, puede (y sugiero a ) tener una débil puntero al temporizador:

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

    Sobre invalidar tienes forma de hacerlo es correcta.

    • Pero entonces, ¿no sería el runloop mantener una fuerte referencia para el temporizador, el temporizador de mantener una fuerte referencia a self y dealloc nunca se producen?
    • Al menos se evita el retener el ciclo. Todavía me acuerdo que el uso de una débil temporizador es mejor.
    • es correcto, no evitar el retener el ciclo. Mientras el temporizador no se invalida el objeto nunca va a morir, y si sólo dealloc invalida el temporizador, nunca podrá ser anulada. Una débil temporizador es inútil y casi nunca lo que desea.
    • El temporizador también podría invocar un weakSelf selector.
    • Apple Temporizador de programación guía no está de acuerdo con usted: developer.apple.com/library/content/documentation/Cocoa/…
    • No, no. Se dice que «Porque el correr de bucle mantiene el temporizador«, lo que significa que el runloop mantiene el temporizador vivo. No importa si usted lo mantiene vivo, no morirá, como mucho como está programado y la manera oficial de cancelar la planificación de un temporizador es invalida. También dice: «laUn temporizador mantiene una fuerte referencia a su destino.«, así que mientras el temporizador está viva, se mantendrá su objetivo de vida, también. Así que haciendo un temporizador débil soluciona nada, a menos que lo invalida en algún momento.

  7. 0

    Swift 4 de la versión. Invalidar debe ser llamada antes de la dealloc.

    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()
        }
    }

    Uso

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

    Con la teoría y la práctica.Tommy solución no es trabajo.

    Teóricamente,__débiles instancia es el parámetro,En la aplicación de

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

    objetivo se conservan todavía.

    Puede implementar un proxy ,que mantienen la referencia débil y adelante selector de llamar a sí mismo , y, a continuación, pasar a la representación como la de destino. Como YYWeakProxy.

Dejar respuesta

Please enter your comment!
Please enter your name here