iphone - scheduled - Mejor momento para invalidar NSTimer dentro de UIViewController para evitar el ciclo de retenciĆ³n
timer.scheduledtimer swift 4 example (9)
¿Alguien sabe cuándo es el mejor momento para detener un NSTimer retenido como referencia dentro de un UIViewController para evitar retener el ciclo entre el temporizador y el controlador?
Aquí está la pregunta en más detalles: Tengo un NSTimer dentro de un UIViewController.
Durante ViewDidLoad del controlador de vista, inicio el temporizador:
statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateStatus) userInfo: nil repeats: YES];
Lo anterior hace que el temporizador mantenga una referencia al controlador de vista.
Ahora quiero liberar mi controlador (el controlador principal lo libera, por ejemplo)
la pregunta es: ¿dónde puedo poner la llamada en [statusTimer invalidate] para forzar al temporizador a liberar la referencia al controlador?
Intenté ponerlo en ViewDidUnload, pero no se dispara hasta que la vista recibe una advertencia de memoria, por lo que no es un buen lugar. Probé con dealloc, pero nunca se llamará a dealloc mientras el temporizador esté vivo (problema de huevo y pollo).
¿Alguna buena sugerencia?
Para empezar, puede evitar el ciclo de retención , por ejemplo, apuntando el temporizador a un objeto
StatusUpdate
que contiene una referencia no retenida (débil) a su controlador, o haciendo que unStatusUpdater
que se inicializa con un puntero a su controlador, contenga una referencia débil a eso, y configura el temporizador para usted.Puede tener la vista detener el temporizador en
-willMoveToWindow:
cuando la ventana de destino esnil
(que debe manejar el contraejemplo para-viewDidDisappear:
que proporcionó), así como en-viewDidDisappear:
Esto significa que su vista está llegando a su controlador; puede evitar-view:willMoveToWindow:
para tomar el temporizador simplemente envíe el controlador a-view:willMoveToWindow:
message o publicando una notificación, si le importa.Presumiblemente, usted es el que causa que la vista se elimine de la ventana, por lo que podría agregar una línea para detener el temporizador junto a la línea que desaloja la vista.
Podría usar un temporizador no repetitivo. Se invalidará tan pronto como se dispare. Luego puede probar en la devolución de llamada si se debe crear un nuevo temporizador no repetitivo y, de ser así, crearlo. El ciclo de retención no deseado solo mantendrá el temporizador y el par de controladores hasta la próxima fecha de disparo. Con una fecha de incendio de 1 segundo, no tendría mucho de qué preocuparse.
Cada sugerencia, pero la primera es una forma de vivir con el ciclo de retención y romperlo en el momento apropiado. La primera sugerencia en realidad evita el ciclo de retención.
El método -viewDidDisappear puede ser lo que estás buscando. Se invoca siempre que la vista esté oculta o descartada.
Escribí una clase de "referencia débil" por exactamente esta razón. Subclasifica NSObject, pero reenvía todos los métodos que NSObject no admite a un objeto de destino. El temporizador retiene el punto débil, pero el punto débil no retiene su objetivo, por lo que no hay ciclo de retención.
El destino llama a [weakref clear] y [timer invalidate] más o menos en dealloc. Icky, ¿no es así?
(La próxima cosa obvia es escribir tu propia clase de temporizador que maneje todo esto por ti).
Puede escribir este código en la función dealloc del controlador de vista
por ej.
-(void)dealloc
{
if([statusTimer isValid])
{
[statusTimer inValidate];
[statustimer release];
statusTimer = nil;
}
}
De esta forma, el contador de referencia de Statustimer disminuirá automáticamente en 1 y también los datos en la memoria asignada también se borrarán.
también puede escribir este código en - (void)viewDidDisappear:(BOOL)animated
función - (void)viewDidDisappear:(BOOL)animated
Puedes probar con - (void)viewDidDisappear:(BOOL)animated
y luego debes validarlo nuevamente en - (void)viewDidAppear:(BOOL)animated
Si el temporizador.REPEAT se establece en YES
, el propietario del temporizador (por ejemplo, ver el controlador o la vista) no será desasignado hasta que el temporizador se invalide.
La solución a esta pregunta es encontrar un punto de activación para detener tu temporizador.
Por ejemplo, inicio un temporizador para reproducir imágenes GIF animadas en una vista, y el punto de activación sería:
- cuando la vista se agrega a la supervista, inicie el temporizador
- cuando se elimina la vista de la supervista, detenga el cronómetro
entonces elijo el UIView
de willMoveToWindow:
como tal:
- (void)willMoveToWindow:(UIWindow *)newWindow {
if (self.animatedImages && newWindow) {
_animationTimer = [NSTimer scheduledTimerWithTimeInterval:_animationInterval
target:self selector:@selector(drawAnimationImages)
userInfo:nil repeats:YES];
} else {
[_animationTimer invalidate];
_animationTimer = nil;
}
}
Si su temporizador es propiedad de un ViewController, tal vez viewWillAppear:
y viewWillDisappear:
son un buen lugar para iniciar y detener el temporizador.
Tuve exactamente el mismo problema y, al final, decidí anular el método de lanzamiento del Controlador de Vista para buscar el caso especial de retenciónCount siendo 2 y mi temporizador en ejecución. Si el temporizador no se estaba ejecutando, esto habría causado que la cuenta de liberación caiga a cero y luego llame a dealloc.
- (oneway void) release {
// Check for special case where the only retain is from the timer
if (bTimerRunning && [self retainCount] == 2) {
bTimerRunning = NO;
[gameLoopTimer invalidate];
}
[super release];
}
Prefiero este enfoque porque lo mantiene simple y encapsulado dentro del único objeto, es decir, el Controlador de Vista y, por lo tanto, es más fácil de depurar. No me gusta, sin embargo, perder el tiempo con la cadena de retención / liberación, pero no puedo encontrar una forma de evitar esto.
Espero que esto ayude y si encuentras un mejor enfoque me encantaría escucharlo también.
Dave
EDITAR: Debería haber sido - (unidireccional)
Una forma de evitarlo es hacer que el NStimer contenga una referencia débil a su UIViewController. Creé una clase que contiene una referencia débil a su objeto y reenvía las llamadas a eso:
#import <Foundation/Foundation.h>
@interface WeakRefClass : NSObject
+ (id) getWeakReferenceOf: (id) source;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
@property(nonatomic,assign) id source;
@end
@implementation WeakRefClass
@synthesize source;
- (id)init{
self = [super init];
// if (self) {
// }
return self;
}
+ (id) getWeakReferenceOf: (id) _source{
WeakRefClass* ref = [[WeakRefClass alloc]init];
ref.source = _source; //hold weak reference to original class
return [ref autorelease];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [[self.source class ] instanceMethodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:self.source ];
}
@end
y lo usas así:
statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: [WeakRefClass getWeakReferenceOf:self] selector: @selector(updateStatus) userInfo: nil repeats: YES];
Su método dealloc se llama (a diferencia de antes) y dentro de él solo llama:
[statusTimer invalidate];
invalidar el temporizador dentro - (vacío) viewWillDisappear: (BOOL) animado funcionó para mí