Lavorare con la classe NSOperationQueue

Il multitasking impedisce alle app di congelarsi. Nella maggior parte dei linguaggi di programmazione, ottenere questo risultato è un po 'complicato, ma la classe NSOperationQueue in iOS rende tutto più semplice!

Questo tutorial dimostrerà come usare il NSOperationQueue classe. Un oggetto NSOperationQueue è una coda che gestisce oggetti di NSOperation tipo di classe. Un oggetto NSOperation, semplicemente formulato, rappresenta una singola attività, compresi i dati e il codice relativi all'attività. NSOperationQueue gestisce e gestisce l'esecuzione di tutti gli oggetti NSOperation (le attività) che sono stati aggiunti ad esso. L'esecuzione avviene con il thread principale dell'applicazione. Quando un oggetto NSOperation viene aggiunto alla coda, viene eseguito immediatamente e non lascia la coda fino al termine. Un'attività può essere annullata, ma non viene rimossa dalla coda finché non è terminata. La classe NSOperation è astratta e quindi non può essere utilizzata direttamente nel programma. Invece, ci sono due sottoclassi fornite, la NSInvocationOperation classe e il NSBlockOperation classe. Userò il primo in questo tutorial.


Il progetto di esempio

Ecco l'obiettivo di questo tutorial: per ogni thread in più vogliamo che la nostra applicazione crei un oggetto NSInvocationOperation (NSOperation). Aggiungeremo ogni oggetto in NSOperationQueue e quindi abbiamo finito. La coda si occupa di tutto e l'app funziona senza congelamento. Per dimostrare chiaramente l'uso delle classi che ho citato sopra, creeremo un progetto di esempio (semplice) in cui, oltre al thread principale dell'applicazione, avremo altri due thread in esecuzione con esso. Sul primo thread, un ciclo verrà eseguito da 1 a 10.000.000 e ogni 100 passaggi un'etichetta verrà aggiornata con il valore del contatore del ciclo. Sul secondo thread, lo sfondo di un'etichetta si riempirà di un colore personalizzato. Tale processo avverrà all'interno di un ciclo e verrà eseguito più di una volta. Quindi avremo qualcosa come un rotatore di colori. Allo stesso tempo, i valori RGB del colore di sfondo personalizzato insieme al valore del contatore del ciclo verranno visualizzati accanto all'etichetta. Infine, utilizzeremo tre pulsanti per cambiare il colore di sfondo della vista sul thread principale. Queste attività non possono essere eseguite contemporaneamente senza multi-tasking. Ecco uno sguardo al risultato finale:


Passaggio 1: crea il progetto

Iniziamo creando il progetto. Apri Xcode e creane uno nuovo Applicazione vista singola.

Fare clic su Avanti e impostare un nome per il progetto. L'ho chiamato ThreadingTestApp. Puoi usare lo stesso o qualsiasi altro nome che ti piace.

Il prossimo. completa la creazione del progetto.


Passaggio 2: installazione dell'interfaccia

Clicca sul ViewController.xib file per rivelare l'Interface Builder. Aggiungi i seguenti controlli per creare un'interfaccia come l'immagine successiva:

  1. UINavigationBar
    • Frame (x, y, W, H): 0, 0, 320, 44
    • Tintcolor: colore nero
    • Titolo: "Demo multi-threading semplice"
  2. UILabel
    • Frame (x, y, W, H): 20, 59, 280, 21
    • Testo: "Counter at Thread # 1"
  3. UILabel
    • Cornice (x, y, W, H): 20, 88, 280, 50
    • Colore di sfondo: colore grigio chiaro
    • Colore del testo: colore grigio scuro
    • Testo: -
  4. UILabel
    • Frame (x, y, W, H): 20, 154, 280, 21
    • Testo: "Random Color Rotator at Thread # 2"
  5. UILabel
    • Frame (x, y, W, H): 20, 183, 100, 80
    • Colore di sfondo: colore grigio chiaro
    • Testo: -
  6. UILabel
    • Frame (x, y, W, H): 128, 183, 150, 80
    • Testo: -
  7. UILabel
    • Frame (x, y, W, H): 20, 374, 280, 21
    • Testo: "Colore di sfondo nella filettatura principale"
  8. UIButton
    • Frame (x, y, W, H): 20, 403, 73, 37
    • Titolo: "Colore n. 1"
  9. UIButton
    • Frame (x, y, W, H): 124, 403, 73, 37
    • Titolo: "Colore # 2"
  10. UIButton
    • Frame (x, y, W, H): 228, 403, 73, 37
    • Titolo: "Colore # 3"

Per l'ultima UILabel e i tre UIButton, imposta il ridimensionamento automatico valore a Sinistra - In basso per rendere l'interfaccia piacevole su iPhone 4 / 4S e iPhone 5, proprio come l'immagine successiva:


Passaggio 3: Proprietà IBOutlet e metodi IBAction

Nel prossimo passaggio creeremo le proprietà IBOutlet e i metodi IBAction necessari per far funzionare la nostra app di esempio. Per creare nuove proprietà e metodi e collegarli ai tuoi controlli mentre sei l'Interface Builder, fai clic sul pulsante centrale del pulsante Editor sulla barra degli strumenti Xcode per visualizzare Assistente editore:

Non tutti i controlli hanno bisogno di una proprietà di presa. Ne aggiungeremo uno solo per UILabels 3, 5 e 6 (in base all'ordine in cui erano elencati nel passaggio 2), di nome label1, label2 e label3.

Per inserire una nuova proprietà outlet, Controllo + clic (clic destro) su un'etichetta> Fare clic sul nuovo punto di riferimento> Trascinare e rilasciare nell'Assistente. Dopodiché, specifica un nome per la nuova proprietà, proprio come nelle seguenti immagini:

Inserimento di una nuova proprietà IBOutlet


Impostazione del nome della proprietà IBOutlet

Ripeti il ​​processo più di tre volte per connettere i tre UILabels alle proprietà. Dentro il tuo ViewController.h file hai dichiarato queste proprietà:

 @property (retain, nonatomic) IBOutlet UILabel * label1; @property (retain, nonatomic) IBOutlet UILabel * label2; @property (retain, nonatomic) IBOutlet UILabel * label3;

Ora aggiungi i metodi IBAction per i tre UIButtons. Ogni pulsante cambierà il colore di sfondo della vista. Per inserire un nuovo metodo IBAction, Ctrl + clic (clic destro) su un UIButton> Fare clic su Ritocca all'interno> Trascina e rilascia nell'Assistente. Successivamente, specifica un nome per il nuovo metodo. Dai un'occhiata alle seguenti immagini e al frammento successivo per i nomi dei metodi:


Inserimento di un nuovo metodo IBAction
Impostazione del nome del metodo IBAction

Ancora una volta, ripetere il processo sopra tre volte per connettere ogni UIButton a un metodo di azione. Il ViewController.h il file dovrebbe ora contenere questi:

 - (IBAction) applyBackgroundColor1; - (IBAction) applyBackgroundColor2; - (IBAction) applyBackgroundColor3;

Le proprietà IBOutlet e i metodi IBAction sono ora pronti. Ora possiamo iniziare a programmare.


Passaggio 4: l'oggetto NSOperationQueue e le dichiarazioni dei metodi correlate alle attività necessarie

Uno dei compiti più importanti che dobbiamo fare è dichiarare a NSOperationQueue oggetto (la nostra coda di operazioni), che verrà utilizzato per eseguire le nostre attività nei thread secondari. Apri il ViewController.h file e aggiungere il seguente contenuto subito dopo @interfaccia intestazione (non dimenticare le parentesi graffe):

 @interface ViewController: UIViewController NSOperationQueue * operationQueue; 

Inoltre, ogni attività deve avere almeno un metodo che contenga il codice che verrà eseguito contemporaneamente al thread principale. Secondo la descrizione introduttiva, il primo compito sarà chiamato il metodo counterTask e il secondo sarà nominato colorRotatorTask:

 -(Vuoto) counterTask; - (void) colorRotatorTask;

Questo è tutto ciò di cui abbiamo bisogno. Nostro ViewController.h il file dovrebbe assomigliare a questo:

 @interface ViewController: UIViewController NSOperationQueue * operationQueue;  @property (retain, nonatomic) IBOutlet UILabel * label1; @property (retain, nonatomic) IBOutlet UILabel * label2; @property (retain, nonatomic) IBOutlet UILabel * label3; - (IBAction) applyBackgroundColor1; - (IBAction) applyBackgroundColor2; - (IBAction) applyBackgroundColor3; - (void) counterTask; - (void) colorRotatorTask; @fine

Passiamo all'attuazione.


Passaggio 5: implementazione

Abbiamo quasi finito. Abbiamo impostato la nostra interfaccia, effettuato tutte le connessioni necessarie, dichiarato qualsiasi IBAzione necessaria e altri metodi, e stabilito la nostra base. Ora è tempo di costruire su di loro.

Apri il ViewController.m file e vai al viewDidLoad metodo. La parte più importante di questo tutorial si svolgerà qui. Creeremo un nuovo NSOperationQueue istanza e due NSOperation (NSInvocationOperation) oggetti. Questi oggetti incapsuleranno il codice dei due metodi precedentemente dichiarati e quindi verranno eseguiti da soli da NSOperationQueue. Ecco il codice:

 - (void) viewDidLoad [super viewDidLoad]; // Crea una nuova istanza NSOperationQueue. operationQueue = [NSOperationQueue new]; // Crea un nuovo oggetto NSOperation usando la sottoclasse NSInvocationOperation. // Digli di eseguire il metodo counterTask. NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector (counterTask) object: nil]; // Aggiungi l'operazione alla coda e lasciala eseguire. [operationQueue addOperation: operation]; [versione di operazione]; // La stessa storia di sopra, basta dire qui per eseguire il metodo colorRotatorTask. operation = [[NSInvocationOperation alloc] initWithTarget: self-selector: @selector (colorRotatorTask) oggetto: nil]; [operationQueue addOperation: operation]; [versione di operazione]; 

L'intero processo è davvero semplice. Dopo aver creato il NSOperationQueue Per esempio, creiamo un oggetto NSInvocationOperation (operazione). Impostiamo il suo metodo di selezione (il codice che vogliamo eseguito su un thread separato) e quindi lo aggiungiamo alla coda. Una volta che entra nella coda, inizia immediatamente a funzionare. Successivamente l'oggetto operazione può essere rilasciato, poiché la coda è responsabile della sua gestione da ora in poi. In questo caso creiamo un altro oggetto e lo useremo allo stesso modo per la seconda attività (colorRotatorTask).

Il nostro prossimo compito è quello di implementare i due metodi di selezione. Iniziamo scrivendo il counterTask metodo. Conterrà un per loop che verrà eseguito per un numero elevato di iterazioni e ogni 100 passi label1il testo verrà aggiornato con il valore contatore dell'iterazione corrente (io). Il codice è semplice, quindi ecco tutto:

 -(void) counterTask // Crea un ciclo GRANDE e ogni 100 passaggi consente di aggiornare la UILabel label1 con il valore del contatore. per (int i = 0; i<10000000; i++)  if (i % 100 == 0)  // Notice that we use the performSelectorOnMainThread method here instead of setting the label's value directly. // We do that to let the main thread to take care of showing the text on the label // and to avoid display problems due to the loop speed. [label1 performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:@"%d", i] waitUntilDone:YES];   // When the loop gets finished then just display a message. [label1 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #1 has finished." waitUntilDone:NO]; 

Si prega di notare che è raccomandato come best practice (anche da Apple) per eseguire qualsiasi aggiornamento visivo sull'interfaccia usando il thread principale e non facendolo direttamente da un thread secondario. Pertanto, l'uso del performSelectorOnMainThread metodo è necessario in casi come questo.

Ora implementiamo il colorRotatorTask metodo:

 -(void) colorRotatorTask // Abbiamo bisogno di un colore personalizzato con cui lavorare. UIColor * customColor; // Esegue un ciclo con 500 iterazioni. per (int i = 0; i<500; i++)  // Create three float random numbers with values from 0.0 to 1.0. float redColorValue = (arc4random() % 100) * 1.0 / 100; float greenColorValue = (arc4random() % 100) * 1.0 / 100; float blueColorValue = (arc4random() % 100) * 1.0 / 100; // Create our custom color. Keep the alpha value to 1.0. customColor = [UIColor colorWithRed:redColorValue green:greenColorValue blue:blueColorValue alpha:1.0]; // Change the label2 UILabel's background color. [label2 performSelectorOnMainThread:@selector(setBackgroundColor:) withObject:customColor waitUntilDone:YES]; // Set the r, g, b and iteration number values on label3. [label3 performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:@"Red: %.2f\nGreen: %.2f\nBlue: %.2f\Iteration #: %d", redColorValue, greenColorValue, blueColorValue, i] waitUntilDone:YES]; // Put the thread to sleep for a while to let us see the color rotation easily. [NSThread sleepForTimeInterval:0.4];  // Show a message when the loop is over. [label3 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #2 has finished." waitUntilDone:NO]; 

Puoi vedere che abbiamo usato il performSelectorOnMainThread metodo anche qui. Il prossimo passo è il [NSThread sleepForTimeInterval: 0.4]; comando, che viene utilizzato per causare un breve ritardo (0,4 secondi) in ogni esecuzione del ciclo. Anche se non è necessario utilizzare questo metodo, è preferibile utilizzarlo qui per rallentare la velocità di modifica del colore dello sfondo del label2 UILabel (il nostro rotatore di colori). Inoltre, in ogni ciclo creiamo valori casuali per il rosso, il verde e il blu. Quindi impostiamo questi valori per produrre un colore personalizzato e impostarlo come colore di sfondo nel label2 UILabel.

A questo punto sono pronti i due task che verranno eseguiti contemporaneamente con il thread principale. Implementiamo i tre (molto facili) metodi IBAction e poi siamo pronti per partire. Come ho già detto, i tre UIButton cambieranno il colore di sfondo della vista, con l'obiettivo finale di dimostrare come il thread principale può essere eseguito insieme alle altre due attività. Eccoli:

 - (IBAction) applyBackgroundColor1 [self.view setBackgroundColor: [UIColor colorWithRed: 255.0 / 255.0 green: 204.0 / 255.0 blue: 102.0 / 255.0 alpha: 1.0]];  - (IBAction) applyBackgroundColor2 [self.view setBackgroundColor: [UIColor colorWithRed: 204.0 / 255.0 verde: 255.0 / 255.0 blu: 102.0 / 255.0 alpha: 1.0]];  - (IBAction) applyBackgroundColor3 [self.view setBackgroundColor: [UIColor whiteColor]]; 

Questo è tutto! Ora puoi eseguire l'applicazione e vedere come possono svolgersi contemporaneamente tre diversi compiti. Ricorda che quando l'esecuzione degli oggetti NSOperation è finita, lascerà automaticamente la coda.


Conclusione

Molti di voi potrebbero aver già scoperto che il codice effettivo per eseguire un'app multi-tasking richiede solo poche righe di codice. Sembra che il maggior carico di lavoro stia implementando i metodi richiesti che funzionano con ciascuna attività. Tuttavia, questo metodo è un modo semplice per sviluppare app multi-threading in iOS.