Werken met de klasse NSOperationQueue

Multi-tasking voorkomt dat apps bevriezen. In de meeste programmeertalen is dit een beetje lastig, maar de klasse NSOperationQueue in iOS maakt het gemakkelijk!

Deze tutorial laat zien hoe je de NSOperationQueue klasse. Een NSOperationQueue-object is een wachtrij die objecten van de. Afhandelt NSOperation klasse type. Een NSOperation-object, eenvoudig geformuleerd, vertegenwoordigt een enkele taak, inclusief zowel de gegevens als de code met betrekking tot de taak. De NSOperationQueue behandelt en beheert de uitvoering van alle NSOperation-objecten (de taken) die eraan zijn toegevoegd. De uitvoering vindt plaats met de rode draad van de applicatie. Wanneer een NSOperation-object aan de wachtrij wordt toegevoegd, wordt het onmiddellijk uitgevoerd en verlaat het de wachtrij pas als het is voltooid. Een taak kan worden geannuleerd, maar deze wordt pas uit de wachtrij verwijderd als deze is voltooid. De NSOperation-klasse is een abstracte, zodat deze niet rechtstreeks in het programma kan worden gebruikt. In plaats daarvan zijn er twee geboden subklassen, de NSInvocationOperation klasse en de NSBlockOperation klasse. Ik gebruik de eerste in deze zelfstudie.


Het voorbeeldproject

Dit is het doel van deze tutorial: voor elke extra thread willen we dat onze applicatie een NSInvocationOperation (NSOperation) -object maakt. We zullen elk object toevoegen aan de NSOperationQueue en dan zijn we klaar. De wachtrij zorgt voor alles en de app werkt zonder te bevriezen. Om het gebruik van de hierboven genoemde klassen duidelijk te demonstreren, zullen we een (eenvoudig) voorbeeldproject maken waarin, behalve de hoofdthread van de app, er nog twee andere threads bij horen. Op de eerste thread loopt een lus van 1 tot 10.000.000 en om de 100 stappen wordt een label bijgewerkt met de tellerwaarde van de lus. Op de tweede thread wordt de achtergrond van een label gevuld met een aangepaste kleur. Dat proces zal binnen een lus plaatsvinden en het zal meer dan eens worden uitgevoerd. Dus we zullen zoiets als een kleurenrotator hebben. Tegelijkertijd worden de RGB-waarden van de aangepaste achtergrondkleur samen met de waarde van de lusteller naast het label weergegeven. Ten slotte zullen we drie knoppen gebruiken om de achtergrondkleur van de weergave op de hoofdthread te veranderen. Deze taken kunnen niet tegelijkertijd worden uitgevoerd zonder multi-tasking. Hier is een blik op het eindresultaat:


Stap 1: Maak het project

Laten we beginnen met het maken van het project. Open de Xcode en maak een nieuwe Toepassing enkele weergave.

Klik op Volgende en stel een naam in voor het project. Ik heb het genoemd ThreadingTestApp. U kunt dezelfde of een andere naam gebruiken die u wilt.

Next. voltooi het maken van het project.


Stap 2: Stel de interface in

Klik op de ViewController.xib bestand om de Interface Builder te onthullen. Voeg de volgende besturingselementen toe om een ​​interface zoals de volgende afbeelding te maken:

  1. UINavigationBar
    • Frame (x, y, W, H): 0, 0, 320, 44
    • Tintcolor: zwarte kleur
    • Titel: "Eenvoudige multi-threading demo"
  2. UILabel
    • Frame (x, y, W, H): 20, 59, 280, 21
    • Tekst: "Counter at Thread # 1"
  3. UILabel
    • Frame (x, y, W, H): 20, 88, 280, 50
    • Achtergrondkleur: lichtgrijze kleur
    • Tekstkleur: donkergrijze kleur
    • Tekst: -
  4. UILabel
    • Frame (x, y, W, H): 20, 154, 280, 21
    • Tekst: "Random Color Rotator bij Thread # 2"
  5. UILabel
    • Frame (x, y, W, H): 20, 183, 100, 80
    • Achtergrondkleur: lichtgrijze kleur
    • Tekst: -
  6. UILabel
    • Frame (x, y, W, H): 128, 183, 150, 80
    • Tekst: -
  7. UILabel
    • Frame (x, y, W, H): 20, 374, 280, 21
    • Tekst: "Achtergrondkleur op hoofdpagina"
  8. UIButton
    • Frame (x, y, W, H): 20, 403, 73, 37
    • Titel: "Kleur # 1"
  9. UIButton
    • Frame (x, y, W, H): 124, 403, 73, 37
    • Titel: "Kleur # 2"
  10. UIButton
    • Frame (x, y, W, H): 228, 403, 73, 37
    • Titel: "Kleur # 3"

Stel het. Voor het laatste UILabel en de drie UIB-knoppen in automatisch aanpassen waarde voor Links onderaan om de interface er leuk uit te laten zien op de iPhone 4 / 4S en iPhone 5, net als de volgende afbeelding:


Stap 3: IBOutlet-eigenschappen en IBAction-methoden

In deze volgende stap zullen we de IBOutlet-eigenschappen en IBAction-methoden maken die nodig zijn om onze voorbeeldapp te laten werken. Als u nieuwe eigenschappen en methoden wilt maken en deze wilt verbinden met uw besturingselementen terwijl u de interfacebouwer bent, klikt u op de middelste knop van de Editor-knop op de Xcode-werkbalk om de Assistent redacteur:

Niet elke besturing heeft een outlet-eigenschap nodig. We zullen er slechts één toevoegen voor de UILabels 3, 5 en 6 (volgens de volgorde waarin ze in stap 2 werden vermeld), genaamd label1, label2 en label3.

Om een ​​nieuwe outlet-eigenschap in te voegen, Bedien + klik (klik met de rechtermuisknop) op een label> klik op de nieuwe verwijzingsuitgang> slepen en neerzetten in de assistent-editor. Geef daarna een naam op voor de nieuwe eigenschap, net als in de volgende afbeeldingen:

Een nieuwe IBOutlet-eigenschap invoegen


De IBOutlet-eigenschapsnaam instellen

Herhaal het bovenstaande proces drie keer om de drie UILabels met eigenschappen te verbinden. In jouw ViewController.h bestand heb je deze eigenschappen gedeclareerd:

 @property (behouden, niet-atomair) IBOutlet UILabel * label1; @property (behouden, niet-atomair) IBOutlet UILabel * label2; @eigenschap (behouden, niet-atomair) IBOutlet UILabel * label3;

Voeg nu de IBAction-methoden voor de drie UIButtons toe. Elke knop verandert de achtergrondkleur van de weergave. Om een ​​nieuwe IBAction-methode in te voegen, Control + klik (klik met de rechtermuisknop) op een UIButton> klik op de Touch-up binnenzijde> slepen en neerzetten in de assistent-editor. Geef daarna een naam op voor de nieuwe methode. Bekijk de volgende afbeeldingen en het volgende fragment voor de methode-namen:


Een nieuwe IBAction-methode invoegen
De naam van de IBAction-methode instellen

Nogmaals, herhaal het bovenstaande proces drie keer om elke UIButton aan een actiemethode te koppelen. De ViewController.h bestand zou nu deze moeten bevatten:

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

De IBOutlet-eigenschappen en IBAction-methoden zijn nu gereed. We kunnen nu beginnen met coderen.


Stap 4: Het NSOperationQueue-object en de noodzakelijke taakgerelateerde methodeverklaringen

Een van de belangrijkste taken die we moeten doen, is een a declareren NSOperationQueue object (onze operatiewachtrij), die zal worden gebruikt om onze taken in secundaire threads uit te voeren. Open de ViewController.h bestand en voeg de volgende inhoud direct na de @interface header (vergeet de accolades niet):

 @interface ViewController: UIViewController NSOperationQueue * operationQueue; 

Bovendien moet elke taak ten minste één methode hebben die de code bevat die gelijktijdig met de hoofdthread wordt uitgevoerd. Volgens de inleidende beschrijving, zal de eerste taak de methode genoemd worden counterTask en de tweede zal een naam krijgen colorRotatorTask:

 -(Void) counterTask; - (void) colorRotatorTask;

Dat is alles wat we nodig hebben. Onze ViewController.h bestand zou er als volgt uit moeten zien:

 @interface ViewController: UIViewController NSOperationQueue * operationQueue;  @property (behouden, niet-atomair) IBOutlet UILabel * label1; @property (behouden, niet-atomair) IBOutlet UILabel * label2; @eigenschap (behouden, niet-atomair) IBOutlet UILabel * label3; - (IBAction) applyBackgroundColor1; - (IBAction) applyBackgroundColor2; - (IBAction) applyBackgroundColor3; - (void) counterTask; - (void) colorRotatorTask; @einde

Laten we verder gaan met de implementatie.


Stap 5: Implementatie

We zijn bijna klaar. We hebben onze interface opgezet, alle benodigde verbindingen gemaakt, alle benodigde IBAction en andere methoden aangegeven en onze basis vastgesteld. Nu is het tijd om daarop voort te bouwen.

Open de ViewController.m bestand en ga naar de viewDidLoad methode. Het belangrijkste deel van deze tutorial gaat hier plaatsvinden. We zullen een nieuwe maken NSOperationQueue bijvoorbeeld en twee NSOperation (NSInvocationOperation) voorwerpen. Deze objecten kapselen de code in van de twee methoden die we eerder hebben verklaard en vervolgens worden ze door het systeem zelf uitgevoerd NSOperationQueue. Hier is de code:

 - (void) viewDidLoad [super viewDidLoad]; // Maak een nieuwe NSOperationQueue-instantie. operationQueue = [NSOperationQueue nieuw]; // Maak een nieuw NSOperation-object met de subklasse NSInvocationOperation. // Vertel het om de methode counterTask uit te voeren. NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector (counterTask) object: nihil]; // Voeg de bewerking toe aan de wachtrij en laat deze uitvoeren. [operationQue addOperation: operation]; [bediening vrijgave]; // Hetzelfde verhaal als hierboven, vertel het hier om de colorRotatorTask-methode uit te voeren. operation = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector (colorRotatorTask) object: nil]; [operationQue addOperation: operation]; [bediening vrijgave]; 

Dit hele proces is heel eenvoudig. Na het maken van de NSOperationQueue Zo maken we bijvoorbeeld een NSInvocationOperation-object (bewerking). We zetten de selector-methode (de code die we willen uitvoeren op een aparte thread), en dan voegen we het toe aan de wachtrij. Zodra het in de wachtrij komt, begint het onmiddellijk te lopen. Hierna kan het bedieningsobject worden vrijgegeven, omdat de wachtrij verantwoordelijk is voor de afhandeling vanaf nu. In dit geval maken we een ander object en we zullen het op dezelfde manier gebruiken voor de tweede taak (colorRotatorTask).

Onze volgende taak is om de twee selectormethoden te implementeren. Laten we beginnen met het schrijven van de counterTask methode. Het bevat een voor lus die voor een groot aantal iteraties wordt uitgevoerd en elke 100 stappen de label1tekst zal worden bijgewerkt met de tellerwaarde van de huidige iteratie (ik). De code is eenvoudig, dus hier is alles:

 -(void) counterTask // Maak een GROTE lus en laat elke 100 stappen het label bijwerken1 UILabel met de waarde van de teller. voor (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]; 

Merk op dat het aanbevolen wordt (ook door Apple) om visuele updates op de interface uit te voeren met behulp van de hoofdthread en niet door het rechtstreeks uit een secundaire thread te doen. Daarom is het gebruik van de performSelectorOnMainThread methode is nodig in gevallen zoals deze.

Laten we nu het colorRotatorTask methode:

 -(void) colorRotatorTask // We hebben een aangepaste kleur nodig om mee te werken. UIColor * customColor; // Voer een lus uit met 500 iteraties. voor (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]; 

Je kunt zien dat we de performSelectorOnMainThread methode hier ook. De volgende stap is de [NSThread sleepForTimeInterval: 0.4]; opdracht, die wordt gebruikt om een ​​korte vertraging (0,4 seconden) te veroorzaken bij elke lusuitvoering. Hoewel het niet nodig is om deze methode te gebruiken, heeft het de voorkeur om het hier te gebruiken om de veranderende snelheid van de achtergrondkleur in de label2 UILabel (onze kleurenrotator). Bovendien maken we in elke lus willekeurige waarden voor het rood, groen en blauw. Vervolgens stellen we deze waarden in om een ​​aangepaste kleur te produceren en deze in te stellen als achtergrondkleur in de label2 UILabel.

Op dit punt zijn de twee taken die tegelijkertijd met de hoofdthread worden uitgevoerd gereed. Laten we de drie (heel eenvoudige) IBAction-methoden implementeren en dan zijn we klaar om te gaan. Zoals ik al zei, zullen de drie UIButtons de achtergrondkleur van de weergave veranderen, met als uiteindelijk doel aan te tonen hoe de hoofdthread naast de andere twee taken kan lopen. Daar zijn ze:

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

Dat is het! Nu kunt u de toepassing uitvoeren en zien hoe drie verschillende taken tegelijkertijd kunnen plaatsvinden. Vergeet niet dat wanneer de uitvoering van NSOperation-objecten is voltooid, deze automatisch de wachtrij verlaat.


Conclusie

Velen van jullie hebben misschien al ontdekt dat de feitelijke code om een ​​multi-tasking-app uit te voeren slechts enkele regels code vereist. Het lijkt erop dat de grootste werklast is de implementatie van de vereiste methoden die werken met elke taak. Niettemin is deze methode een gemakkelijke manier om multi-threading-apps te ontwikkelen in iOS.