【iOS】Ejecutar bucle

Prefacio: ¿Qué es RunLoop?

¿ Qué es RunLoopcorrer vueltas? Eso es literalmente lo que significa.

La documentación oficial de Apple explica estoRunLoop

RunLoop es parte de la estructura básica estrechamente relacionada con los subprocesos. RunLoop es un bucle de eventos que programa tareas y procesa tareas. El propósito de RunLoop es mantener el hilo ocupado cuando hay trabajo y ponerlo en estado de suspensión cuando no hay trabajo.

La razón por la que las aplicaciones de iOS pueden continuar respondiendo y mantener el programa en ejecución es que existe un Event Loopmecanismo de bucle de eventos (): un mecanismo en el que los subprocesos pueden responder y manejar eventos en cualquier momento. Este mecanismo requiere que los subprocesos no puedan salir para completar el evento de manera eficiente. programación y tratamiento.

En iOS, este mecanismo de bucle de eventos se llamaRunLoop

RunLoopEn realidad, es un objeto. El objeto maneja varios eventos que ocurren durante la ejecución del programa (como eventos táctiles, eventos de actualización de la interfaz de usuario, eventos del temporizador, eventos Selector) en un bucle para mantener el programa en ejecución y permitir que el programa ingrese cuando haya No hay procesamiento de eventos Estado de suspensión, ahorrando así recursos de la CPU para mejorar el rendimiento del programa.

Principio RunLoop del hilo principal por defecto

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    
    
    NSString * appDelegateClassName;
    @autoreleasepool {
    
    
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

return UIApplicationMain(argc, argv, nil, appDelegateClassName);Este es el bucle de ejecución principal de una aplicación iOS, que maneja eventos de usuario, actualizaciones de interfaz y la lógica principal de la aplicación. UIApplicationMainLa función crea el objeto de la aplicación y el bucle de ejecución principal, y pasa el control a la clase delegada de la aplicación ( AppDelegate) para manejar la lógica de la aplicación.

La función interna UIApplicationMainnos ayuda a iniciar el hilo principal RunLoop.
UIApplicationMainHay un bucle infinito de código en su interior.

function loop() {
    
    
    initialize();
    do {
    
    
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

El programa siempre do-whilese ejecutará en un bucle.

RunLoopDiagrama del modelo oficial de Apple.

Insertar descripción de la imagen aquí

RunLoopEs un bucle en el hilo, que RunLoopdetecta continuamente en el bucle, espera recibir mensajes a través de dos fuentes Input sources(fuente de entrada) y Timer sources(fuente de tiempo), y luego procesa el hilo de notificación de eventos recibido y descansa cuando no hay ningún evento.

1. Objeto RunLoop

RunLoopLos objetos son objetos encapsulados basados ​​en tipos CFFoundation框de fotogramas .CFRunLoopRef

  • NSRunLoopSe basa en CFRunLoopRefla encapsulación y proporciona API orientadas a objetos, pero estas API no son seguras para subprocesos.
[NSRunLoop currentRunLoop];//获得当前RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象

CoreFoundationCFRunLoopRefobjeto de marco

  • CFRunLoopRefEstá CoreFoundationdentro del marco y proporciona una API de funciones puras del lenguaje C. Todas estas API son seguras para subprocesos.
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象

Insertar descripción de la imagen aquí

Entonces las dos formas correspondientes son:

- (void)getRunLoop {
    
    
    NSRunLoop *runloop  = [ NSRunLoop currentRunLoop];
    NSRunLoop *manRlp = [NSRunLoop mainRunLoop];
    
    CFRunLoopRef cfRlp = CFRunLoopGetCurrent();
    CFRunLoopRef mainCfRlp = CFRunLoopGetMain();
}

Veamos la implementación específica de CFRunLoopGetCurrenty CFRunLoopGetMain:

CFRunLoopRef CFRunLoopGetCurrent(void) {
    
    
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

CFRunLoopRef CFRunLoopGetMain(void) {
    
    
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

Se descubre que esta función se llama en el proceso _CFRunLoopGet0, que se explicará más adelante.

Parte del código fuente de CFRunLoopRef (relacionada con la introducción de subprocesos)

Parte del código fuente de CFRunLoopRef

struct __CFRunLoop {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort; //【通过该函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop】
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread; //【RunLoop对应的线程】
    uint32_t _winthread;
    CFMutableSetRef _commonModes; // 【存储的是字符串,记录所有标记为common的mode】
    CFMutableSetRef _commonModeItems;//【存储所有commonMode的item(source、timer、observer)】
    CFRunLoopModeRef _currentMode;//【当前运行的mode】
    CFMutableSetRef _modes;//【存储的是CFRunLoopModeRef】
    struct _block_item *_blocks_head;//【do blocks时用到】
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

Además de algunos atributos, la atención debe centrarse en tres variables miembro

pthread_t _pthread;【RunLoop对应的线程】
CFRunLoopModeRef _currentMode;【当前运行的mode】
CFMutableSetRef _modes;【存储的是CFRunLoopModeRef】

Eche un vistazo a la relación entre RunLoop y los subprocesos.

2. RunLoop e hilos

Primero echemos un vistazo a _CFRunLoopGet0cómo se implementa esta función y RunLoopqué tiene que ver con los subprocesos.

//全局的Dictionary,key是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0是始终有效的“主线程”的同义词

//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
    
    if (pthread_equal(t, kNilPthreadT)) {
    
    
    //pthread为空时,获取主线程
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
    
    
        __CFSpinUnlock(&loopsLock);
        //第一次进入时,创建一个临时字典dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //根据传入的主线程获取主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    //保存主线程,将主线程-key和RunLoop-Value保存到字典中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    
    
    //释放dict
        CFRelease(dict);
    }
    //释放mainRunLoop
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //以上说明,第一次进来的时候,不管是getMainRunLoop还是get子线程的runLoop,主线程的runLoop总是会被创建

	//从全局字典里获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
    
    
    //如果取不到,就创建一个新的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //创建好之后,以线程为key,runLoop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runLoop
    if (!loop) {
    
    
    //把newLoop存入字典__CFRunLoops,key是线程t
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
    }

	//如果传入线程就是当前线程
    if (pthread_equal(t, pthread_self())) {
    
    
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
    
    
        //注册一个回调,当线程销毁时,销毁对应的RunLoop
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

Este código fuente nos dice:

  • Cada hilo tiene un RunLoopobjeto correspondiente único.
  • RunLoopGuardado en un Dictionaryhilo global como key, RunLoopcomoValue
  • No hay ningún objeto recién creado por el hilo RunLoop. RunLoopEl hilo se creará RunLooppor primera vez y se destruirá cuando finalice.
  • El hilo principal RunLoopse obtuvo (creó) automáticamente y el hilo secundario no está habilitado de forma predeterminada RunLoop.

3. Clases relacionadas con RunLoop

Hay RunLoop5 clases relacionadas.

  • CFRunLoopRef: representa RunLoopel objeto
  • CFRunLoopModeRef: representa RunLoopel modo de funcionamiento de
  • CFRunLoopSourceRef: RunLoopFuentes de entrada mencionadas en el modelo.
  • CFRunLoopTimerRef: fuente de sincronización

Insertar descripción de la imagen aquí

  • Cada vez que se llama a la función principal, solo se permite especificar RunLoopuno de los modos de operación ( ), el cual se llama ;CFRunLoopModeRefCurrentMode
  • Si necesita cambiar Mode, solo puede salir Loopy volver a entrar Mode, esto es principalmente para separar diferentes grupos de fuentes de entrada ( CFRunLoopSourceRef), fuentes de sincronización ( CFRunLoopTimerRef) y observadores ( CFRunLoopObserverRef) para que no se afecten entre sí.
  • Si no hay modeninguno de Sourcr/Timer/Observerellos, RunLoopsaldrá directamente sin entrar al bucle.

RunLoopLa estructura es la misma que la de una muñeca matrioska, en RunLoopsu interior hayModeModeSouce / Observer / Timer

Implementación de clases relacionadas con RunLoop

Uno RunLoopcontiene varios Modey cada uno Modecontiene varios Source/Timer/Observer.
Esta oración es en realidad la relación entre 5 clases relacionadas.

CFRunLoopModeRef

Representa RunLoopel modo de funcionamiento, pero aclare el concepto aquí. Podemos RunLoopinstalar varios en él Mode, pero debemos especificar uno al especificar la operación Mode.

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名称,运行模式是通过名称来识别的
    Boolean _stopped; //mode是否被终止
    char _padding[3];
    //整个结构体最核心的部分
------------------------------------------
    CFMutableSetRef _sources0;//Sources0
    CFMutableSetRef _sources1;//Sources1
    CFMutableArrayRef _observers;//观察者
    CFMutableArrayRef _timers;//定时器
------------------------------------------
    CFMutableDictionaryRef _portToV1SourceMap;//字典    key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    CFIndex _observerMask;
};
  • Un CFRunLoopModeRefobjeto tiene nameatributos, varios source0,,, y , se puede ver que los eventos son source1administrados y responsables de la administración .timerobserverportmodeRunLoopMode

Cinco modos de funcionamiento

Cinco registrados por el sistema por defecto Mode:

  • kCFRunLoopDefaultMode: Valor predeterminado de la aplicación Mode, normalmente el hilo principal Modese ejecuta bajo este.
  • UITrackingRunLoopMode: Seguimiento de interfaz Mode, utilizado para ScrollViewrastrear el deslizamiento táctil para garantizar que la interfaz no se vea afectada por otras interacciones Mode(eventos de interacción con el usuario Mode).
  • UIInitializationRunLoopMode: El primero que ingrese cuando inicie la aplicación por primera vez Modeya no se usará una vez que se complete el inicio y se cambiará a kCFRunLoopDefaultMode.
  • GSEventReceiveRunLoopMode: Interno para aceptar eventos del sistema Mode, generalmente no se usa.
  • kCFRunLoopCommonModes: Este es un marcador de posición Mode, usado como marcador kCFRunLoopDefaultModey UITrackingRunLoopModeno real Mode(pseudomodo, no modo real).

Entre ellos kCFRunLoopDefaultMode, se encuentran los patrones que debemos utilizar en el desarrollo UITrackingRunLoopMode.kCFRunLoopCommonModes

Modos comunes

En RunLoopel objeto, hay una CommonModesvariable miembro al frente.

//简化版本
struct __CFRunLoop {
    
    
    pthread_t _pthread;
    CFMutableSetRef _commonModes;//存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;//当前运行的mode
    CFMutableSetRef _modes;//存储的是CFRunLoopModeRef对象,不同mode类型,它的mode名字不同
};
  • Uno Modepuede marcarse a sí mismo como Commonuna propiedad ModeNameagregándolo a RunLoopun archivo commonModes.
  • Siempre que RunLoopcambie el contenido de un elemento, se RunLoopsincronizará con todos los elementos con la etiqueta ._commonModeItemsSource/Observer/TimerCommonMode

Su principio subyacente

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    
    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    
    
    //获取所有的_commonModeItems
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    //获取所有的_commonModes
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
    
    
        CFTypeRef context[2] = {
    
    rl, modeName};
        //将所有的_commonModeItems逐一添加到_commonModes里的每一个Mode
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set);
    }
    } else {
    
    
    }
    __CFRunLoopUnlock(rl);
}

En general, la función principal de este método es agregar una commonModeItemscolección específica a la colección de patrones comunes del ciclo de ejecución y commonModeItemsagregarla a cada una en la colección de patrones comunes Modepara garantizar que el origen del evento del patrón común esté disponible en Modemúltiples y pueda procesarse. . Implica CoreFoundationlas operaciones subyacentes del ciclo de ejecución en el marco y se utiliza para gestionar eventos y patrones en el ciclo de ejecución.

CFRunLoopSourceRef

CFRunLoopSourceRef: RunLoopLa fuente de entrada mencionada en el modelo, que es donde ocurre el evento.

struct __CFRunLoopSource {
    
    
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;   //执行顺序
    CFMutableBagRef _runLoops;//包含多个RunLoop
    //版本
    union {
    
    
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

Acabo de mencionar soucela existencia source0y source1dos versiones anteriores, ¿qué hicieron respectivamente?

  • Source0Solo contiene una devolución de llamada (puntero de función), que no puede activar eventos de forma activa. Al usarlo, primero debe llamarlo CFRunLoopSourceSignal(source), Sourcemarcarlo como pendiente y luego llamarlo manualmente CFRunLoopWakeUp(runloop)para activarlo RunLoopy permitir que maneje este evento.
  • Source1Contiene uno mach_porty una devolución de llamada (puntero de función), que el kernel y otros subprocesos pueden utilizar para enviarse mensajes entre sí, lo que Sourcepuede activar activamente RunLoopel subproceso.
  • ⚠️: buttonEl evento de clic pertenece al Source0contenido de ejecución de la función. Se procesa el evento de clic Source0.
    Source1Se utiliza para recibir y distribuir eventos y distribuirlos Souce0para su procesamiento.

CFRunLoopTimerRef

CFRunLoopTimerRef: Fuente de sincronización: disparador basado en tiempo.

CFRunLoopTimerRefes un disparador basado en el tiempo que NSTimerse puede mezclar con . Contiene una duración y una devolución de llamada (puntero de función). Cuando se una RunLoop, RunLoopregistrará el punto de tiempo correspondiente, y cuando llegue el punto de tiempo, RunLoopse despertará para ejecutar esa devolución de llamada.

cuando llamamos NSTimer_scheduledTimerWithTimeInterval

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

El sistema agregará automáticamente NSDefaltRunLoopMode.

Igual al siguiente código

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

El deslizamiento del temporizador es inexacto

En Zhihu Daily, el deslizamiento tableViewprovocó la falla del temporizador del gráfico de carrusel automático de arriba.
Un problema común es: cuando deslizamos NSTimerpara realizar algo cada vez UIScrollView, NSTimerse pausará, y cuando dejemos de deslizar, NSTimerse reanudará nuevamente.

La razón es:

Cuando no hacemos nada, RunLoopestamos NSDefaultRunLoopModecaídos.
Cuando arrastramos y soltamos, RunLoopfinaliza NSDefaultRunLoopModey cambia al UITrackingRunLoopModemodo. No hay ninguna adición en este modo NSTimer, por lo que el nuestro NSTimerno funcionará.
Cuando soltamos el mouse, RunLoopfinaliza UITrackingRunLoopModeel modo y vuelve al NSDefaultRunLoopModemodo, por lo que NSTimercomienza a funcionar normalmente nuevamente.

resolver:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

En iOS, cuando desliza el dedo UITableViewu otra vista de desplazamiento, el hilo principal RunLoopcambia a UITrackingRunLoopMode, que es un modo de bucle de ejecución especial que se utiliza para manejar eventos de interacción del usuario, como gestos de desplazamiento. De forma predeterminada, si usa un temporizador en el hilo principal, se ejecutará NSDefaultRunLoopModeen el modo de bucle de ejecución predeterminado. Dado que RunLoopsolo se puede procesar un modo de bucle de ejecución a la vez, cuando se desliza, NSDefaultRunLoopModese cambia a UITrackingRunLoopMode, lo que hace que el evento del temporizador se detenga hasta que finalice la diapositiva.

Para resolver este problema, puedes usar kCFRunLoopCommonModes. Representa un " common mode set", que incluye tanto NSDefaultRunLoopModecomo UITrackingRunLoopMode. Al usarlo junto con un temporizador kCFRunLoopCommonModes, el temporizador se puede activar tanto en el modo predeterminado como en el modo de seguimiento, evitando así el problema de deslizamiento que provoca que el temporizador se detenga.

CFRunLoopObserverRef

CFRunLoopObserverRefEs un observador, cada uno de los cuales Observercontiene una devolución de llamada (puntero de función). Cuando RunLoopel estado cambia, el observador puede recibir el cambio a través de la devolución de llamada.

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;//监听的RunLoop
    CFIndex _rlCount;//添加该Observer的RunLoop对象个数
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;//同时间最多只能监听一个
    CFRunLoopObserverCallBack _callout;//监听的回调
    CFRunLoopObserverContext _context;//上下文用于内存管理
};

//观测的时间点有一下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    
    
    kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

¿Qué es el elemento de modo?

Mode¿Qué tipos de elementos están incluidos? Como se
mencionó anteriormente CFMutableSetRef _commonModeItems: almacenar commonModetodo item( source,, )timerobserver

Los // Sourceanteriores se denominan colectivamenteTimerObservermode item

mode itemSe pueden agregar todos Mode, Modese pueden incluir varios mode itemy itemse puede agregar uno a varios mode. Pero cuando itemse añade uno al mismo repetidamente, modeno tendrá ningún efecto. Si no hay ninguno modede itemellos, RunLoopsaldrá sin entrar al bucle.

Insertar descripción de la imagen aquí

Principio de implementación del sueño RunLoop

Cambie del modo de usuario al modo kernel, deje que el hilo duerma en el modo kernel, active el hilo cuando haya un mensaje y regrese al modo de usuario para procesar el mensaje.

Resumen de ejecución de bucle

El interior de RunLoop es en realidad un bucle do while. Cuando se llama a CFRunLoopRun(), el hilo permanecerá en este bucle. La función solo regresará cuando se agote el tiempo de espera o se llame manualmente.

  • RunLoopLa ejecución debe especificar uno modey el modeevento de la tarea debe registrarse.
  • RunLoopSe modeejecuta de forma predeterminada. Por supuesto, también puede especificar un tipo de modeoperación, pero solo se puede modeejecutar bajo un tipo.
  • RunLoopEn realidad, un bucle se mantiene internamente do-whiley el hilo permanecerá en este bucle hasta que expire el tiempo de espera o se detenga manualmente.
  • RunLoopEl núcleo es uno mach_msg() . RunLoopLlame a esta función para recibir mensajes. Si nadie más envía portun mensaje, el núcleo pondrá el hilo en estado de espera; de lo contrario, el hilo procesará el evento.

4. Aplicación práctica de RunLoop

  1. Controlar el ciclo de vida del subproceso (el subproceso se mantiene vivo)
  2. Solucionar el problema por el cual NSTimer deja de funcionar al deslizarse
  3. Supervisar el retraso de la aplicación
  4. Optimización del rendimiento

Cómo iniciar RunLoop

- (void)run; // 默认模式
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
  1. ejecutar: se puede iniciar sin ninguna condición. Aunque es simple, no afecta los resultados de control reales. RunLoop es un bucle do while. Si RunLoop se ejecuta incondicionalmente, el hilo se colocará en el bucle para siempre, lo que significa que No tengo forma de controlar el bucle en sí. RunLoop solo se puede detener matando el proceso.
  2. runUnitDate: establece el límite de tiempo.
  • Se establece el tiempo de espera y RunLoop finaliza después de este tiempo.
  1. runMode:beforeDate: comienza en un modo específico.
  • Puede especificar en qué modo se ejecuta runloop, pero es una sola llamada. Cuando se agota el tiempo de espera o se procesa una fuente de entrada, runLoop saldrá automáticamente. Ambos métodos anteriores se llaman cíclicamente.

El primero continuará ejecutándose y siempre estará en modo NSDefaultRunLoopMode, llamando al método runMode:beforeDate: repetidamente.
El segundo método siempre llamará al método runMode:beforeDate: en el modo NSDefaultRunLoopMode antes del tiempo de espera.
El tercer método siempre llamará al método runMode:beforeDate: hasta que se agote el tiempo de espera o se procese la primera fuente de entrada.

Ejecutar bucle cerrado

  1. Establezca la configuración del bucle en ejecución en tiempo de espera.
  2. Deténgase manualmente.

Cabe señalar aquí que, aunque eliminar la fuente de entrada y el temporizador del bucle de ejecución puede hacer que el bucle de ejecución se cierre, este no es un método confiable. El sistema puede agregar fuentes de entrada al bucle de ejecución, pero es posible que no lo sepamos en nuestro código. Estas fuentes de entrada, por lo que no se pueden eliminar, lo que hace que el ciclo de ejecución no pueda salir.

Cuando iniciamos RunLoop a través de los métodos runUnitDate y runMode: beforeDate:, configuramos el tiempo de espera. Sin embargo, si necesitamos tener el control más preciso sobre este hilo y su RunLoop, en lugar de depender del mecanismo de tiempo de espera, podemos finalizar manualmente un RunLoop mediante el método CFRunLoopStop(). Sin embargo, el método CFRunLoopStop() solo finalizará la llamada runMode:beforeDate: que se está ejecutando actualmente, pero no finalizará las llamadas runloop posteriores.

visualización retrasada de imageView

Cuando la interfaz contiene UITableView y hay imágenes en cada UITableViewCell. Aquí es cuando nos desplazamos por UITableView, si hay muchas imágenes que deben mostrarse, puede haber retrasos.

Deberíamos posponer la implementación de imágenes, es decir, ImageView retrasa la visualización de imágenes. No cargues la imagen cuando la deslicemos, muéstrala después de arrastrarla:

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

El usuario hace clic en la pantalla y en el hilo principal, la imagen se muestra después de tres segundos. Sin embargo, después de que el usuario hace clic en la pantalla, si el usuario comienza a desplazarse por la vista de tabla nuevamente, la imagen no se mostrará incluso después de tres segundos. El usuario deja de desplazarse y se mostrará la imagen.

Esto se debe a que el método setImage está restringido para usarse solo en el modo NSDefaultRunLoopMode. Al desplazarse por la vista de tabla, el programa se ejecuta en modo de seguimiento, por lo que no se ejecutará el método setImage.

hilo residente

Durante el desarrollo de una aplicación, si las operaciones en segundo plano son muy frecuentes, como reproducir música en segundo plano, descargar archivos, etc., esperamos que el hilo que ejecuta el código en segundo plano siempre resida en la memoria. Podemos agregar un fuerte referencia para el subproceso de memoria residente, agregue una fuente en el RunLoop del subproceso y active RunLoop:

@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end

 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
 [self.thread start];
- (void)runThread {
    
    
    NSLog(@"开启子线程:%@", [NSThread currentThread]);
// 子线程的RunLoop创建出来需要手动添加事件输入源和定时器 因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
    //下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    // 测试开始RunLoop
    // 未进入循环就会执行该代码
    NSLog(@"failed");
}

// 同时在我们自己新建立的这个线程中写一下touchesBegan这个方法测试点击空白处会不会在子线程相应方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  {
    
    
    [self performSelector:@selector(runTest) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)runTest {
    
    
    NSLog(@"子线程点击空白:%@", [NSThread currentThread]);
}

Puede ver que RunLoop se inicia con éxito e ingresa al bucle. Cuando se hace clic en la pantalla, el método también se llama en el subproceso. De esta manera, el propósito del subproceso residente se logra después de que se inicia el subproceso.

Hilo mantente vivo

Escenario:
cuando normalmente se crea un subproceso secundario, el subproceso se destruirá después de que se ejecuten las tareas en el subproceso.
A veces necesitamos realizar tareas con frecuencia en un subproceso secundario. La creación y destrucción frecuente de subprocesos provocará una gran sobrecarga. En este momento, podemos controlar el ciclo de vida del subproceso a través de runloop.

En el siguiente código, debido a que el método runMode:beforeDate: es una llamada única, debemos agregarle un bucle; de ​​lo contrario, llamar al runloop una vez finalizará y el efecto será el mismo que si no se usara un runloop.

La condición de este bucle se establece en SÍ de forma predeterminada. Cuando se llama al método de detención, se ejecuta el método CFRunLoopStop() para finalizar este runMode:beforeDate:. Al mismo tiempo, la condición en el bucle se establece en NO para detener el bucle y el runloop salen.

@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL stopped;

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor greenColor];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:button];
    [button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"执行任务" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 200, 100, 20);
    
    
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    [stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
    [stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
    stopButton.frame = CGRectMake(100, 400, 100, 20);
    
    self.stopped = NO;
    //防止循环引用
    __weak typeof(self) weakSelf = self;
    
    self.thread = [[NSThread alloc] initWithBlock:^{
    
    
        NSLog(@"Thread---begin");
        
        //向当前runloop添加Modeitem,添加timer、observer都可以。因为如果mode没有item,runloop就会退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
    
    
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"Thread---end");
    }];
    [self.thread start];
}
- (void)pressPrint {
    
    
    //子线程中调用print
    [self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}

//子线程需要执行的任务
- (void)print {
    
    
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

- (void)pressStop {
    
    
    //子线程中调用stop
    if (_stopped == NO ) {
    
    
        [self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    
}

//停止子线程的runloop
- (void)stop {
    
    
    //设置标记yes
    self.stopped = YES;
    
    //停止runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    //解除引用, 停止runloop这个子线程就会dealloc
    self.thread = nil;
}

- (void)dealloc {
    
    
    NSLog(@"%s", __func__);
}

NSTimer no es exacto

En el desarrollo real, el temporizador generalmente no existe en el RunLoop del hilo principal, porque el temporizador del temporizador también causará inexactitud cuando el hilo principal realiza tareas de bloqueo.

Si el temporizador se bloquea en el hilo principal, cómo resolver el problema del temporizador inexacto.

  • Póngalo en un hilo secundario, pero necesita abrir el hilo y controlar el ciclo de vida del hilo, lo cual cuesta mucho.
  • Utilice el temporizador de GCD para evitar el bloqueo.

Supongo que te gusta

Origin blog.csdn.net/m0_63852285/article/details/132107089
Recomendado
Clasificación