Entendiendo NSRunLoop

108

¿Alguien puede explicar qué es NSRunLoop? así que, como sé, NSRunLoophay algo relacionado con NSThread¿verdad? Así que suponga que creo un hilo como

NSThread* th=[[NSThread alloc] initWithTarget:self selector:@selector(someMethod) object:nil];
[th start];

-(void) someMethod
{
    NSLog(@"operation");
}

así que después de que este hilo termine su trabajo, ¿verdad? ¿Por qué usar RunLoopso dónde usar? de los documentos de Apple He leído algo pero no está claro para mí, así que explíquelo lo más simple posible

taffarel
fuente
Esta pregunta tiene un alcance demasiado amplio. Refina tu pregunta a algo más específico.
Jody Hagins
3
Al principio, quiero saber qué hago en genereal NSRunLoop y cómo se conecta con Thread
taffarel

Respuestas:

211

Un ciclo de ejecución es una abstracción que (entre otras cosas) proporciona un mecanismo para manejar las fuentes de entrada del sistema (sockets, puertos, archivos, teclado, mouse, temporizadores, etc.).

Cada NSThread tiene su propio ciclo de ejecución, al que se puede acceder mediante el método currentRunLoop.

En general, no necesita acceder directamente al ciclo de ejecución, aunque hay algunos componentes (de red) que pueden permitirle especificar qué ciclo de ejecución utilizarán para el procesamiento de E / S.

Un bucle de ejecución para un subproceso determinado esperará hasta que una o más de sus fuentes de entrada tengan algún dato o evento, luego activará el controlador de entrada apropiado para procesar cada fuente de entrada que esté "lista".

Después de hacerlo, volverá a su ciclo, procesando la entrada de varias fuentes y "durmiendo" si no hay trabajo por hacer.

Esa es una descripción de nivel bastante alto (tratando de evitar demasiados detalles).

EDITAR

Un intento de abordar el comentario. Lo rompí en pedazos.

  • significa que solo puedo acceder / ejecutar para ejecutar un bucle dentro del hilo, ¿verdad?

En efecto. NSRunLoop no es seguro para subprocesos y solo se debe acceder a él desde el contexto del subproceso que ejecuta el bucle.

  • ¿Hay algún ejemplo simple de cómo agregar un evento para ejecutar un bucle?

Si desea monitorear un puerto, simplemente agregaría ese puerto al bucle de ejecución, y luego el bucle de ejecución observará la actividad de ese puerto.

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode

También puede agregar un temporizador explícitamente con

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • ¿Qué significa que luego volverá a su ciclo?

El ciclo de ejecución procesará todos los eventos listos en cada iteración (según su modo). Deberá consultar la documentación para descubrir los modos de ejecución, ya que eso está un poco más allá del alcance de una respuesta general.

  • ¿Está inactivo el bucle de ejecución cuando inicio el hilo?

En la mayoría de las aplicaciones, el ciclo de ejecución principal se ejecutará automáticamente. Sin embargo, usted es responsable de iniciar el ciclo de ejecución y responder a los eventos entrantes de los hilos que gira.

  • ¿Es posible agregar algunos eventos al ciclo de ejecución del hilo fuera del hilo?

No estoy seguro de lo que quiere decir aquí. No agrega eventos al ciclo de ejecución. Agrega fuentes de entrada y fuentes de temporizador (del hilo que posee el ciclo de ejecución). El bucle de carrera luego los observa para ver si hay actividad. Por supuesto, puede proporcionar entrada de datos de otros subprocesos y procesos, pero la entrada será procesada por el ciclo de ejecución que está monitoreando esas fuentes en el subproceso que está ejecutando el ciclo de ejecución.

  • ¿Significa que a veces puedo usar el bucle de ejecución para bloquear el hilo por un tiempo?

En efecto. De hecho, un bucle de ejecución "permanecerá" en un controlador de eventos hasta que ese controlador de eventos haya regresado. Puede ver esto en cualquier aplicación de manera bastante simple. Instale un controlador para cualquier acción de E / S (por ejemplo, presionar un botón) que está inactivo. Bloqueará el ciclo de ejecución principal (y toda la interfaz de usuario) hasta que se complete ese método.

Lo mismo se aplica a cualquier ciclo de ejecución.

Le sugiero que lea la siguiente documentación sobre ciclos de ejecución:

https://developer.apple.com/documentation/foundation/nsrunloop

y cómo se utilizan en los hilos:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1

Jody Hagins
fuente
2
significa que solo puedo acceder / ejecutar para ejecutar un bucle dentro del hilo, ¿verdad? ¿Hay algún ejemplo simple de cómo agregar un evento para ejecutar un bucle? ¿Qué significa que luego volverá a su ciclo? ¿Está inactivo el bucle de ejecución cuando inicio el hilo? ¿Es posible agregar algunos eventos al ciclo de ejecución del hilo fuera del hilo? ¿Significa que a veces puedo usar el bucle de ejecución para bloquear el hilo por un tiempo?
taffarel
"Instale un controlador para cualquier acción de IO (por ejemplo, presionar un botón) que duerme". ¿Quieres decir que si continúo presionando el botón con el dedo, continuará bloqueando el hilo por un tiempo?
Miel
No. Lo que quiero decir es que el runloop no procesa nuevos eventos hasta que el controlador se ha completado. Si duerme (o realiza alguna operación que lleva mucho tiempo) en el controlador, el ciclo de ejecución se bloqueará hasta que el controlador haya completado su trabajo.
Jody Hagins
@taffarel ¿ es posible agregar algunos eventos al ciclo de ejecución del hilo fuera del hilo? Si eso significa "puedo hacer que el código se ejecute en el bucle de ejecución de otro hilo a voluntad", entonces la respuesta es sí. Simplemente llame performSelector:onThread:withObject:waitUntilDone:, pase un NSThreadobjeto y su selector se programará en el bucle de ejecución de ese hilo.
Mecki
12

Los bucles de ejecución son lo que separa las aplicaciones interactivas de las herramientas de línea de comandos.

  • Las herramientas de línea de comandos se inician con parámetros, ejecutan su comando y luego salen.
  • Las aplicaciones interactivas esperan la entrada del usuario, reaccionan y luego continúan esperando.

Desde aqui

Le permiten esperar hasta que el usuario toque y responder en consecuencia, espere hasta que obtenga un controlador de finalización y aplique sus resultados, espere hasta que obtenga un temporizador y realice una función. Si no tiene un runloop, entonces no puede estar escuchando / esperando los toques del usuario, no puede esperar hasta que se produzca una llamada de red, no puede despertar en x minutos a menos que use DispatchSourceTimeroDispatchWorkItem

También de este comentario :

Los subprocesos en segundo plano no tienen sus propios ciclos de ejecución, pero puede agregar uno. Por ejemplo, AFNetworking 2.x lo hizo. Fue una técnica probada y verdadera para NSURLConnection o NSTimer en subprocesos en segundo plano, pero ya no hacemos mucho esto nosotros mismos, ya que las API más nuevas eliminan la necesidad de hacerlo. Pero parece que URLSession sí, por ejemplo, aquí hay una solicitud simple , ejecutando [ver el panel izquierdo de la imagen] controladores de finalización en la cola principal, y puede ver que tiene un ciclo de ejecución en el hilo de fondo


Específicamente sobre: ​​"Los subprocesos en segundo plano no tienen sus propios ciclos de ejecución". El siguiente temporizador no se activa para un envío asíncrono :

class T {
    var timer: Timer?

    func fireWithoutAnyQueue() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
            print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
        })
    }

    func fireFromQueueAsnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — async") // failed to print
            })
        }
    }

    func fireFromQueueSnyc() {
        let queue = DispatchQueue(label: "whatever")
        queue.sync {
            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from a queue — sync") // success. Weird. Read my possible explanation below
            })
        }
    }

    func fireFromMain() {
        DispatchQueue.main.async {
            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
                print("from main queue — sync") //success
            })
        }
    }
}

Creo que la razón por la que el syncbloque también se ejecuta es porque:

Los bloques de sincronización generalmente solo se ejecutan desde su cola de origen . En este ejemplo, la cola de origen es la cola principal, la cola cualquiera es la cola de destino.

Para probarlo, inicié sesión RunLoop.currenten cada despacho.

El envío de sincronización tenía el mismo bucle de ejecución que la cola principal. Mientras que RunLoop dentro del bloque asíncrono era una instancia diferente de las demás. Es posible que esté pensando cómo por qué RunLoop.currentdevuelve un valor diferente. ¿¡No es un valor compartido !? ¡Gran pregunta! Leer más:

NOTA IMPORTANTE:

La propiedad de clase current NO es una variable global.

Devuelve el ciclo de ejecución del hilo actual .

Es contextual. Es visible solo dentro del alcance del hilo, es decir , almacenamiento local del hilo . Para obtener más información al respecto, consulte aquí .

Este es un problema conocido con los temporizadores. No tiene el mismo problema si usaDispatchSourceTimer

Miel
fuente
8

RunLoops son como una caja donde las cosas simplemente suceden.

Básicamente, en un RunLoop, vas a procesar algunos eventos y luego regresas. O regrese si no procesa ningún evento antes de que se agote el tiempo de espera. Puede decirlo como similar a NSURLConnections asincrónicas, procesando datos en segundo plano sin interferir en su bucle actual y, al mismo tiempo, necesita datos sincrónicamente. Lo cual se puede hacer con la ayuda de RunLoop que hace que su asincrónicoNSURLConnection y proporciona datos en el momento de la llamada. Puedes usar un RunLoop como este:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
    loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}

En este RunLoop, se ejecutará hasta que complete algunos de sus otros trabajos y configure YourBoolFlag en falso .

Del mismo modo, puede usarlos en subprocesos.

Espero que esto te ayude.

Akshay Sunderwani
fuente
0

Los bucles de ejecución son parte de la infraestructura fundamental asociada con los subprocesos. Un ciclo de ejecución es un ciclo de procesamiento de eventos que se utiliza para programar el trabajo y coordinar la recepción de eventos entrantes. El propósito de un ciclo de ejecución es mantener su hilo ocupado cuando hay trabajo por hacer y ponerlo en reposo cuando no lo hay.

De aquí


La característica más importante de CFRunLoop es CFRunLoopModes. CFRunLoop trabaja con un sistema de "Run Loop Sources". Las fuentes se registran en un bucle de ejecución para uno o varios modos, y el bucle de ejecución en sí está hecho para ejecutarse en un modo determinado. Cuando un evento llega a una fuente, solo es manejado por el ciclo de ejecución si el modo de la fuente coincide con el modo actual del ciclo de ejecución.

De aquí

dengApro
fuente