Análisis (001) NSTimer

NSTimer

 

o un contador de tiempo es periódicamente envía un mensaje a un especificado en un momento determinado del objeto de destino.

Timer es un patrón específico de hilo notifica su propia manera de hacer algo, temporizadores y su Runloop relacionados. Si el modo de temporizador en el que el actual no es el monitoreo Runloop, el temporizador no comenzará a funcionar hasta la próxima Runloop en el modo apropiado. Si Runloop no está funcionando, entonces el temporizador no se iniciará.

ciclo de vida

NSTimer objetivo será el exterior pasó a retener. Si se trata de una llamada de una sola vez (se repite: NO), se invalida a sí mismo después de esta llamada, y NSTimer retener ese objetivo hará un comunicado.

Sin embargo, si se repite llamadas, necesitamos propia invalidate manualmente, o bien NSTimer persisten.

precisión

No es real mecanismo

Temporizador de la duplicación del trabajo puede variar en función de la hora programada en lugar de en tiempo real la programación de su propio funcionamiento. Por ejemplo, si el temporizador está programado para comenzar en un momento determinado y repetir cinco segundos, el temporizador se iniciará después de ese momento en particular de 5 segundos, incluso en ese tiempo de disparo en particular retrasa. Si el temporizador se retrasa por lo que se ha perdido una o más tiempo de disparo, el temporizador se activará el próximo evento más próximo a comenzar, mientras que la parte posterior se ejecute de acuerdo con el intervalo de disparo Normal 

Para el temporizador UIScrollView

Al utilizar el método NSTimer de scheduledTimerWithTimeInterval. De hecho en este momento que se añadirá al hilo actual del temporizador bucle de ejecución, y el modo por defecto es el NSDefaultRunLoopMode. Y si el hilo actual es el hilo principal, que es el hilo de interfaz de usuario, algunos eventos de interfaz de usuario, tales como la operación de arrastre UIScrollView, cambiará al modo de NSEventTrackingRunLoopMode bucle de ejecución, en el proceso, el evento modo NSDefaultRunLoopMode por defecto no se ha registrado será ejecutado. En otras palabras, esta vez utilizando scheduledTimerWithTimeInterval añadido a Runloop el temporizador no se ejecutará.

 

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];     
//使用NSRunLoopCommonModes模式,把timer加入到当前Run Loop中。  
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

 

 

NSTimer en el aprendizaje, una serie de problemas de acabado.

Un impulso a la página B por página, pasar la página NSTimer en B, entonces pop volver a la página de A y no encontró dealloc ejecución. Esta vez el temporizador sigue en funcionamiento, causando pérdidas de memoria.

Código es el siguiente:

#import "LJLNSTimerViewController.h"
#import "DeviceHeader.h"
#import "LJLWeakProxy.h"

static int timerNum = 0;
@interface LJLNSTimerViewController ()

@property(nonatomic, strong) NSTimer * timer;
@property(nonatomic, strong) NSThread *thread;
@property(nonatomic, assign) BOOL stopTimer;

@end

@implementation LJLNSTimerViewController

- (void)viewDidLoad {

    [super viewDidLoad];

//一、这样写pop的时候不会调用dealloc,造成内存泄露
//    因为LJLNSTimerViewController 与 timer 互相强引用
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];

//    NSDefaultRunLoopMode 默认运行循环模式
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

-(void)timerRun{
    timerNum++;
    NSLog(@"%s 触发计时器%d",__func__,timerNum);
}

- (void)dealloc{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}

página B es LJLNSTimerViewController

  1. Primero de todo lo que sabe, NSTimer se está ejecutando en NSRunLoop.
  2. Ninguna llamada dealloc, B página de descripción no hay liberación.
  3. A continuación, la línea continua representa una fuerte referencia. A medida que el contador de tiempo y TimerVC referencias fuertes el uno al otro, se crea una referencia circular, no puede ser liberado correctamente.

Un intento:

En primer lugar creo que sin duda las propiedades NSTimer en una referencia débil, fuerte en el débil.

@property(nonatomic, weak) NSTimer * timer;

Pero se encontraron y no hay ningún efecto, porque esta vez, aunque no hay ninguna referencias circulares, pero la referencia Runloop a este temporizador, y el temporizador tiene referencias fuertes este TimerViewController, pop cuando se apunta el puntero TimerViewcontroller destruido, pero el temporizador puntero que apunta TimerViewcontroller no se destruye, por lo todavía existen pérdidas de memoria, no se pueden realizar dealloc.

Trate de dos

Ya que no puedo pasar directamente a uno mismo, a continuación, tratar de pasar weakSelf

__weak typeof(self) weakSelf = self;  
self.timer = [NSTimer scheduledTimerWithTimeInterval:1  target:weakSelf selector:@selector(timerAction:) userInfo:nil repeats:true]; 

Los resultados del ensayo ocurrieron referencia circular, B no es liberado, temporizador de esta variable es fuertemente referencia weakSelf, temporizador -> weakSelf -> TimerViewcontroller -> temporizador, se forma entre los tres referencia circular.

Trate de tres

análisis LJProxy puede ser procesada por la adición de un objeto proxy intermedio. Dejar que espera TimerViewcontroller ejemplo LJProxy, vamos a ejemplos LJProxy referencias débiles TimerViewcontroller. temporizador fuerte ejemplo LJProxy referencia.

@interface LJProxy : NSObject
+ (instancetype) proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LJProxy
+ (instancetype) proxyWithTarget:(id)target
{
    LJProxy *proxy = [[LJProxy alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 这里的target发生了变化
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:[LJProxy proxyWithTarget:self] selector:@selector(timerRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
  1. Cuando el primer punto para que aparezca el puntero Nº 1 se destruye, y ahora no hay una fuerte TimerViewcontroller punto de referencia, TimerViewcontroller puede ser adecuadamente destruido.
  2. destrucción TimerViewcontroller método dealloc irá se llama [self.timer Invalidate] dealloc años en; Runloop retirado del temporizador, N ° 3 puntero Destrucción
  3. Cuando TimerViewcontroller, destruyendo su fuerza correspondiente al puntero será destruido por referencia, a continuación, el puntero 2 será destruido.
  4. La ejecución de lo anterior, temporizador no ha sido fuerte referencia a otros objetos, temporizador será destruido, LJProxy será destruido

 

Trate de cuatro

Los datos de la investigación NSProxy saben que hay una clase, que está diseñado para hacer que la clase de mensaje reenviado.

@interface LJLWeakProxy : NSProxy
@property(nonatomic, weak)id target;
+(instancetype)proxyWithTarget:(id)target;
@end


@implementation LJLWeakProxy

+ (instancetype)proxyWithTarget:(id)target{

    LJLWeakProxy * proxy = [LJLWeakProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{

    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    return [invocation invokeWithTarget:self.target];
}
@end

Aquí está la documentación de Apple NSProxy, simplemente proporciona varias informaciones

  • NSProxy está diseñado para hacer una clase de reenvío de mensajes
  • NSProxy es una clase abstracta, y no se requiere para escribir una subclase hereda de NSProxy propia
  • subclases NSProxy necesitan implementar dos métodos, es decir, los dos siguientes
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
     return [self.target methodSignatureForSelector:sel];
} 

- (void)forwardInvocation:(NSInvocation *)invocation {
     [invocation invokeWithTarget:self.target];
}

 

Extendido: mensaje OC lookup mecanismo     OC mecanismo de reenvío de mensajes

  1. Enviar un mensaje:
    1. Comenzar con el caché método para encontrar maneras, no se mire ir a la lista de los métodos de la clase actual, encontrar un método de adición de caché (el inicio del tamaño de la caché es de 4-1, más de tres cuartas partes vacían en la expansión, la expansión en el original 2 4 * 2-1 veces, y luego almacenar en caché el método actual, y luego vaciarse antes de la hora en la caché, y también lo son las dos veces ha sido la expansión).
    2. Si no hay ningún método de instancia de la clase padre para repetir los pasos anteriores para encontrar. Encuentra por el método de la categoría Búsqueda de ISA cadena. Si nos fijamos en el final (o clase base NSObject metaclase raíz) no encontró ese método, entonces se llega a la siguiente etapa 2
  2. Resolución método dinámico:
    1. Para ver si los implementos de clase resolveInstanceMethod: resolveClassMethod:, y si se aplica sobre métodos analíticos añadido dinámicamente, llame al método, si no logrado continúe en el paso 3
  3. reenvío de mensajes de dos pasos
    1. Llamada forwardingTargetForSelector:, objetos para ver si el rendimiento es nulo, por no decir nulas llamando objc_msgSend objeto entrante y SEL;
    2. Si la devolución es nula, entonces se llama al método llamado firma methodSignatureForSelector:, cambio, si la firma del método no es nula, llamada forwardInvocation: para realizar el procedimiento.
  4. Si el mensaje se reenvía no puede manejar, se produce una excepción.

Segundo intento, heredado de objeto NSObject LJProxy. Cuando método implementado no encuentra atrás a través de los pasos anteriores 1,2,3 para lanzar un error, si en el proceso que hacemos medidas correctivas, entonces no va a ser lanzado doesNotRecognizeSelector: El programa puede realizar normal, pero si heredado de la NSProxy LJLWeakProxy, se saltará todos los pasos anteriores, el segundo pequeño paso directamente al paso 3, directamente con el método de ejecución del objeto para un rendimiento mejorado. (NSProxy solamente methodSignatureForSelector: y forwardInvocation: Estos dos métodos, por lo que sólo el primer pequeño paso 3 paso 2)

 

método de cinco

Tomando prestado:

NSTimer es conocido por ser un objetivo parámetro de referencia fuerte: sí Es decir, si se olvide de apagar el temporizador, a continuación, pasar en lo que será una referencia fuerte. función de temporizador es un método de ajuste de temporización y duración de la llamada NSTimer es inexacta! Se colgó en por un runloop interruptor de hilo, el impacto del tiempo de ejecución de un evento.

Usando dispatch_asyn () realiza una función de temporización. Mira el siguiente código.

- (void)loop {
    [self doSomething];
    ......
    // 休息 time 秒,再调 loop,实现定时调用
    [NSThread sleepForTimeInterval:time];
    dispatch_async(self.runQueue, ^{
        [weakSelf loop];
    });    
}

dispatch_async en bucle de sintonía no produce una llamada recursiva
dispatch_async es añadir una tarea en la cola, el MCD de devolución de llamada [bucle weakSelf]

Esta solución no puede liberar el temporizador, cuelgue en cuestión runloop no se puede quitar.

Por lo que se puede escribir usted mismo un temporizador. liberación controlador, el temporizador se detiene automáticamente la liberación de   GitHub Descargar
o abierta en viewWillAppear, cerrado viewWillDisappear. NSTimer referencia fuerte para resolver el problema de la VC no se libera. Muchas maneras de encontrar uno.

 

hilo hijo usando NSTimer

En primer lugar,

//四、子线程添加NSTimer 如果直接pop会内存泄露,需要再pop之前处理停止NSTimer
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        __strong typeof(weakSelf) strongWeakSelf = weakSelf;
        if (strongWeakSelf) {
            strongWeakSelf.thread = [NSThread currentThread];
            [strongWeakSelf.thread setName:@"线程1"];
            strongWeakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:strongWeakSelf selector:@selector(timerRun) userInfo:nil repeats:YES];
            NSRunLoop * runloop = [NSRunLoop currentRunLoop];
            [runloop addTimer:strongWeakSelf.timer forMode:NSDefaultRunLoopMode];
            [runloop run];
        }
    });

- (void)cancelTimer{
    if (self.timer && self.thread) {
        [self performSelector:@selector(cancel) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
}

- (void)cancel{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

No directamente al proceso dealloc. Es necesario añadir manualmente evento pop, pop y luego en la implementación de la destrucción de ir NSTimer.

II.

//五、子线程添加 NSTimer 解决内存泄露问题
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:[LJLWeakProxy proxyWithTarget:self] selector:@selector(timerRun) userInfo:nil repeats:YES] ;

    __weak typeof(self) weakSelf = self;
    self.thread = [[NSThread alloc] initWithBlock:^{
        [[NSRunLoop currentRunLoop] addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
        // 这里需要注意不要使用[[NSRunLoop currentRunLoop] run]
        while (weakSelf && !weakSelf.stopTimer) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [self.thread start];

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self stop];    
}

-(void)stop
{
    if (self.timer && self.thread) {
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
}

-(void)stopThread
{
//    设置标记为YES
    self.stopTimer = YES;
//    停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
//    清空线程
    self.thread = nil;
}

Aquí no utilice [[NSRunLoop currenRunLoop] Ejecutar] para abrir el hilo actual Runloop, pero el uso de RunMode: beforeDate:.

Debido a la apertura por el método run Runloop ningún método correspondiente de parada, y por lo que el tiempo RunMode bucle: beforeDate: ejecutar Runloop.

 

 

 

referencia:

https://www.jianshu.com/p/d4589134358a

https://www.jianshu.com/p/bb691938fb2f

Publicado 83 artículos originales · ganado elogios 12 · vistas 180 000 +

Supongo que te gusta

Origin blog.csdn.net/shengdaVolleyball/article/details/104656411
Recomendado
Clasificación