IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

2. diel - PerformSelector (), run loop a paralelné cyklus vo Swift

V minulej lekcii, Úvod do viacvláknových aplikácií vo Swift , sme si vysvetlili prečo multithreading potrebujeme. Tiež už vieme, že by sme pre neho mali využívať primárne GCD a zabudnúť, že existuje niečo ako trieda Thread, ktorú môžeme využiť a spustiť pomocou nej niečo na pozadí.

Za dobu, čo sa iOS venujem, som prístup pomocou Thread nikdy nestretol a často blogové príspevky o GCD skúsených vývojárov priamo varovali pred používaním Thread.

PerformSelector ()

Existuje ešte jedna alternatívna možnosť, ako na multithreading vo Swift. A je veľmi, veľmi jednoduchá. Skrýva sa v globálnych metódach performSelector(), ktorých signatúra určí, či sa vykonajú na pozadí alebo na hlavnom vlákne.

Môžeme si rovno ukázať príklad na kódu:

performSelector(inBackground: #selector(fetchData), with: nil)

Pretože používame #selector, je nutné, aby volané metóda, v tomto prípade fetchData(), bola označená modifikátorom @objc. Ten ju sprístupní pre Objective-C runtime, teda operačný systém a ten sa o zavolanie postará. To si hneď ukážeme.

Hoci sa jedná o riešenie na jeden riadok kódu, nie je príliš flexibilný. Posielanie parametrov nie je priamočiare a ak chceme s výsledkom pracovať na hlavnom vlákne, musíme toto "prepnutie" umiestniť do volané metódy. fetchData() by mohlo teda vyzerať nasledovne:

@objc func fetchData() {
        let newData = apiService.getNewData()
        tableView.performSelector(onMainThread: #selector(UITableView.reloadData), with: nil, waitUntilDone: false)
}

V tejto fiktívnej ukážke slúži metóda fetchData() k stiahnutiu dát z nejakej webovej služby a tieto dáta chceme užívateľovi následne ukázať v komponente UITableView, takže ju musíme aktualizovať. Pretože sa jedná o prácu s UI, je potrebné dostať sa späť na hlavnú vlákno. Volanie performSelector() je kvapku odlišné, pretože nevoláme metódu z našej triedy (teda nášho ViewControlleru), ale chceme zavolať metódu komponenty UITableView, takže performSelector() voláme na tejto komponente a pomocou #selector vyberieme metódu pre refresh dát. Fungovať to bude korektne.

Teraz si spomeňme na požiadavku s modifikátorom @objc. Bez neho nebude #selector fungovať. Akonáhle by sme tak chceli volať metódu, ktorá nie je naša (takže sa nedá modifikátor pridať) a alebo ho už neobsahuje, tak tento prístup fungovať nebude. performSelector() môže byť fajn voľbou pre jednoduché prevedenie metódy na inom ako hlavnom vlákne. Ja by som odporučil využívať skôr služby DispatchQueue, častejšie si ju vďaka tomu pripomeniete a ponúka mnohonásobne väčšiu flexibilitu.

Run loop

Keď už sa bavíme o DispatchQueue a performSelector(), vysvetlíme si rovno drobnosť zvanú application run loop. Run loop si môžete predstaviť ako nekonečný cyklus, ktorý beží spoločne s aplikáciou s vykonáva všetok kód na hlavnom vlákne.

Prečo o tom hovoríme, keď sme mohli až do teraz nerušene programovať aplikácie, bez toho, aby nás run loop zaujímal? Občas sa pri riešení problémov hodí vedieť, že niečo také existuje. Ak napr. Dôjde ku stlačení tlačidla, tak pre užívateľov a aj programátora je spustenie akcie okamžité, pokiaľ nejde o nejaký asynchrónne kód. Vo vnútri aplikácie sa ale deje niečo trochu iné. Kód naviazaný na tlačidlo pobeží až v novom "kole" run loop. Vo zvyšku súčasného, kedy bolo tlačidlo spustené, totiž okrem iného dôjde k dokreslenie animácie stlačení tlačidla. V opačnom prípade by mohlo dôjsť k zaseknutiu.

Teraz sa práve dostávame k DispatchQueue a performSelector(). Ich okamžité varianty totiž spustí akýkoľvek kód až v nasledujúcom "kole" run loop. Môže to vyzerať nasledovne:

DispatchQueue.main.async {
            // kód poběží v novém run loop
}

Ukážme si aj príklad pre performSelector(), metóda someWork() opäť pobeží až v nasledujúcom run loop:

performSelector(onMainThread: #selector(someWork), with: nil, waitUntilDone: false)

Run loop sa tu venujeme primárne z dôvodu, aby ste minimálne tušili, o čo sa jedná a mali predstavu, prečo treba cudzí kód používa DispatchQueue.main.async, aj keď už beží na hlavnom vlákne.

S týmto problémom sa môžeme často stretnúť u animácií, ktoré bežia po načítaní aplikácie. Trebárs som narazil na situáciu, že som komponente nastavil alpha = 0 v Interface Builder, aby som mohol animovať fade-in efekt pri zapnutí aplikácie a túto animáciu spustil v metóde viewDidAppear(). Môže sa stať, že na nastavenie pôvodnej alpha na nula ešte nedošlo, UIKit uvidí, že nemá čo animovať, pretože komponenta má stále hodnotu alpha na 1 (teda plne viditeľná) a animácie ju má posunúť do rovnakého stavu, takže animáciu preskočí. Nie je sranda niečo také hľadať. A práve tu vám posunutie kódu na ďalší run loop pomôže.

Prevedenie kódu po uplynulej dobe

Občas v aplikáciách potrebujeme vykonať nejaký kód po určitom časovom intervale.

DispatchQueue

DispatchQueue nám okrem iného ponúka jednoduchý mechanizmus, ako niečo vykonať až po uplynutí stanovenej doby. Slúži na to metóda asyncAfter(), čo je prakticky async, ale s parametrom určujúcim ako dlho sa má pred vykonaním čakať.

Jej použitie môže vyzerať nasledovne:

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
      print("Uběhly 2 vteřiny!")
}

Okrem .seconds možno použiť aj .nanoseconds, .microseconds a .miliseconds. Alebo môžeme napísať jednoducho .now() + 0.5, kde 0.5 je počet sekúnd. Tento zápis je častý, .seconds je ale bez premýšľania oveľa jasnejšie. Samozrejme asyncAfter() možno použiť aj na akékoľvek inom queue, nemusí ísť nutne o main.

Perform ()

Využiť môžeme tiež metódu perform() a v parametri zadať v sekundách, ako dlho má pred vykonaním danej metódy čakať. Nevýhodou je, že musíme mať pripravenú @objc metódu, zatiaľ čo asyncAfter() na DispatchQueue zvládne pracovať s akýmkoľvek kódom.

Vykonanie metódy po uplynulom čase s perform() môže vyzerať nasledovne:

perform(#selector(printMessage), with: nil, afterDelay: 2)

@objc func printMessage() {
        print("Uběhly 2 vteřiny!")
}

Paralelné cyklus jednoducho

DispatchQueue nám ponúka jednoduchú možnosť, ako ľubovoľný počet iterácií vykonať paralelne. Príslušná metóda sa volá concurrentPerform() a ako parameter očakáva iba počet iterácií. K číslu iterácie navyše dostaneme prístup v closure, takže môžeme ľahko existujúce for cyklus previesť na concurrentPerform.

Zápis vyzerá napríklad takto:

DispatchQueue.concurrentPerform(iterations: 100) { (i) in
      print(i)
}

concurrentPerform() môže byť skvelé v situáciách, kedy na hlavnom vlákne potrebujeme vykonať zložitejšie for cyklus, ktorý má buď veľa iterácií alebo je každá iterácia náročnejšieho charakteru. Pomocou tejto metódy automaticky využijete maximum možností procesora.

Len pozor na prípad, kedy by ste v opakovaniach potrebovali pristupovať k zdieľaným premenným, pretože to bude brzdiť, pretože vlákna budú musieť čakať, až dôjde k ich uvoľneniu.

Týmto máme teoretickú časť viacvláknového programovania vo Swiftu za sebou a môžeme sa vrhnúť na praktickú ukážku v ďalšej lekcii, Vytvorenie iOS aplikácia pre demonštráciu GCD .


 

Predchádzajúci článok
Úvod do viacvláknových aplikácií vo Swift
Všetky články v sekcii
Paralelné programovanie a viacvláknové aplikácie vo Swift
Preskočiť článok
(neodporúčame)
Vytvorenie iOS aplikácia pre demonštráciu GCD
Článok pre vás napísal Filip Němeček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity