Capire NSRunLoop


108

Qualcuno può spiegare per cosa è NSRunLoop? così come so NSRunLoopè qualcosa connesso con NSThreadgiusto? Quindi supponi che crei un thread come

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

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

quindi dopo che questo thread finisce il suo lavoro, giusto? perché usare RunLoopso dove usare? da documenti Apple ho letto qualcosa ma non è chiaro per me, quindi spiegalo nel modo più semplice possibile


Questa domanda ha una portata troppo ampia. Per favore, restringi la tua domanda a qualcosa di più specifico.
Jody Hagins

3
All'inizio voglio sapere cosa fare in generale NSRunLoop e come si collega a Thread
taffarel

Risposte:


211

Un ciclo di esecuzione è un'astrazione che (tra le altre cose) fornisce un meccanismo per gestire le sorgenti di input del sistema (socket, porte, file, tastiera, mouse, timer, ecc.).

Ogni NSThread ha il proprio ciclo di esecuzione, a cui è possibile accedere tramite il metodo currentRunLoop.

In generale, non è necessario accedere direttamente al ciclo di esecuzione, sebbene vi siano alcuni componenti (di rete) che potrebbero consentire di specificare quale ciclo di esecuzione utilizzeranno per l'elaborazione I / O.

Un ciclo di esecuzione per un dato thread attenderà fino a quando una o più delle sue origini di input non avrà alcuni dati o eventi, quindi attiverà i gestori di input appropriati per elaborare ciascuna fonte di input "pronta".

Dopo averlo fatto, tornerà al suo ciclo, elaborando l'input da varie fonti e "dormendo" se non c'è lavoro da fare.

Questa è una descrizione di livello piuttosto alto (cercando di evitare troppi dettagli).

MODIFICARE

Un tentativo di affrontare il commento. L'ho fatto a pezzi.

  • significa che posso accedere / eseguire solo per eseguire il ciclo all'interno del thread giusto?

Infatti. NSRunLoop non è thread-safe e dovrebbe essere accessibile solo dal contesto del thread che esegue il ciclo.

  • c'è un semplice esempio su come aggiungere un evento per eseguire il ciclo?

Se si desidera monitorare una porta, è sufficiente aggiungere quella porta al ciclo di esecuzione, quindi il ciclo di esecuzione controllerà l'attività della porta.

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

Puoi anche aggiungere un timer esplicitamente con

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
  • cosa significa che tornerà al suo ciclo?

Il ciclo di esecuzione elaborerà tutti gli eventi pronti a ogni iterazione (in base alla sua modalità). Avrai bisogno di guardare la documentazione per scoprire le modalità di esecuzione, poiché è un po 'oltre lo scopo di una risposta generale.

  • run loop è inattivo quando avvio il thread?

Nella maggior parte delle applicazioni, il ciclo di esecuzione principale verrà eseguito automaticamente. Tuttavia, sei responsabile dell'avvio del ciclo di esecuzione e della risposta agli eventi in arrivo per i thread che giri.

  • è possibile aggiungere alcuni eventi al ciclo di esecuzione del thread al di fuori del thread?

Non sono sicuro di cosa intendi qui. Non aggiungi eventi al ciclo di esecuzione. Si aggiungono origini di input e origini timer (dal thread che possiede il ciclo di esecuzione). Il ciclo di corsa quindi li guarda per l'attività. È possibile, ovviamente, fornire l'input di dati da altri thread e processi, ma l'input verrà elaborato dal ciclo di esecuzione che monitora quelle origini sul thread che esegue il ciclo di esecuzione.

  • significa che a volte posso usare run loop per bloccare il thread per un po '

Infatti. Infatti, un ciclo di esecuzione "rimarrà" in un gestore di eventi fino a quando non sarà restituito. Puoi vederlo in qualsiasi app in modo abbastanza semplice. Installa un gestore per qualsiasi azione IO (ad esempio, pressione di un pulsante) che dorme. Bloccherai il ciclo di esecuzione principale (e l'intera interfaccia utente) fino al completamento di quel metodo.

Lo stesso vale per qualsiasi ciclo di esecuzione.

Ti suggerisco di leggere la seguente documentazione sui cicli di esecuzione:

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

e come vengono utilizzati all'interno dei thread:

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


2
significa che posso accedere / eseguire solo per eseguire il ciclo all'interno del thread giusto? c'è un semplice esempio su come aggiungere un evento per eseguire il ciclo? cosa significa che tornerà al suo ciclo? run loop è inattivo quando avvio il thread? è possibile aggiungere alcuni eventi al ciclo di esecuzione del thread al di fuori del thread? significa che a volte posso usare il ciclo di esecuzione per bloccare il thread per un po '?
taffarel

"Installa un gestore per qualsiasi azione IO (ad esempio, pressione di un pulsante) che dorme." Vuoi dire che se continuo a tenere il dito sul pulsante, continuerà a bloccare il thread per un po '?!
Honey

No. Quello che voglio dire è che il runloop non elabora nuovi eventi finché il gestore non ha completato. Se dormi (o esegui un'operazione che richiede molto tempo) nel gestore, il ciclo di esecuzione si bloccherà fino a quando il gestore non avrà completato il suo lavoro.
Jody Hagins

@taffarel è possibile aggiungere alcuni eventi al ciclo di esecuzione del thread al di fuori del thread? Se questo significa "posso far funzionare il codice sul runloop di un altro thread a piacimento", allora la risposta è sì. Basta chiamare performSelector:onThread:withObject:waitUntilDone:, passare un NSThreadoggetto e il tuo selettore verrà programmato sul runloop di quel thread.
Mecki

12

I cicli di esecuzione sono ciò che separa le app interattive dagli strumenti della riga di comando .

  • Gli strumenti della riga di comando vengono avviati con i parametri, eseguono il loro comando, quindi escono.
  • Le app interattive attendono l'input dell'utente, reagiscono, quindi riprendono ad attendere.

Da qui

Ti consentono di attendere fino a quando l'utente tocca e risponde di conseguenza, aspetta fino a quando non ottieni un completamentoHandler e applica i suoi risultati, aspetta fino a quando non ottieni un timer ed esegui una funzione. Se non si dispone di un ciclo di esecuzione, non è possibile ascoltare / attendere i tocchi dell'utente, non è possibile attendere che venga eseguita una chiamata di rete, non è possibile essere svegliati entro x minuti a meno che non si utilizzi DispatchSourceTimeroDispatchWorkItem

Anche da questo commento :

I thread in background non hanno i propri runloops, ma puoi semplicemente aggiungerne uno. Ad esempio AFNetworking 2.x lo ha fatto. Era una tecnica collaudata per NSURLConnection o NSTimer sui thread in background, ma non lo facciamo più da soli, poiché le API più recenti eliminano la necessità di farlo. Ma sembra che URLSession lo faccia, ad esempio, qui c'è una semplice richiesta , che esegue [vedi il pannello sinistro dell'immagine] gestori di completamento sulla coda principale, e puoi vedere che ha un ciclo di esecuzione sul thread in background


In particolare su: "I thread in background non hanno i propri runloops". Il seguente timer non si attiva per una spedizione asincrona :

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

Penso che anche il motivo per cui syncviene eseguito il blocco sia perché:

i blocchi di sincronizzazione di solito vengono eseguiti solo dalla coda di origine . In questo esempio, la coda di origine è la coda principale, la coda qualunque è la coda di destinazione.

Per verificarlo, ho effettuato l'accesso a RunLoop.currentogni spedizione.

L'invio di sincronizzazione aveva lo stesso ciclo di esecuzione della coda principale. Mentre RunLoop all'interno del blocco asincrono era un'istanza diversa dalle altre. Potresti pensare a perché RunLoop.currentrestituisce un valore diverso. Non è un valore condiviso !? Ottima domanda! Continua a leggere:

NOTA IMPORTANTE:

La proprietà della classe current NON è una variabile globale.

Restituisce il ciclo di esecuzione per il thread corrente .

È contestuale. È visibile solo nell'ambito del thread, ovvero archiviazione locale del thread . Per saperne di più vedere qui .

Questo è un problema noto con i timer. Non hai lo stesso problema se usiDispatchSourceTimer


8

I RunLoops sono un po 'come una scatola in cui le cose accadono e basta.

Fondamentalmente, in un RunLoop, vai a elaborare alcuni eventi e poi ritorni. Oppure restituisci se non elabora alcun evento prima che venga raggiunto il timeout. Puoi dirlo come simile a NSURLConnections asincrono, Elaborazione dei dati in background senza interferire con il tuo ciclo corrente e, allo stesso tempo, hai bisogno di dati in modo sincrono. Che può essere fatto con l'aiuto di RunLoop che rende il tuo asincrono NSURLConnectione fornisce dati al momento della chiamata. Puoi usare un RunLoop come questo:

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

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

In questo RunLoop, verrà eseguito fino a quando non completerai alcuni degli altri lavori e imposterai YourBoolFlag su false .

Allo stesso modo, puoi usarli nei thread.

Spero che questo ti aiuti.


0

I cicli di esecuzione fanno parte dell'infrastruttura fondamentale associata ai thread. Un ciclo di esecuzione è un ciclo di elaborazione degli eventi utilizzato per pianificare il lavoro e coordinare la ricezione degli eventi in arrivo. Lo scopo di un ciclo di esecuzione è quello di mantenere il thread occupato quando c'è del lavoro da fare e mettere il thread a dormire quando non ce n'è.

Da qui


La caratteristica più importante di CFRunLoop è CFRunLoopModes. CFRunLoop funziona con un sistema di "Run Loop Sources". Le sorgenti vengono registrate in un ciclo di esecuzione per una o più modalità e il ciclo di esecuzione stesso viene eseguito in una determinata modalità. Quando un evento arriva su una sorgente, viene gestito dal ciclo di esecuzione solo se la modalità della sorgente corrisponde alla modalità corrente del ciclo di esecuzione.

Da qui

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.