Jeśli jesteście początkującymi programistami Swift i nie macie backgroundu w postaci nauki innych języków programowania, na pewno zastanowiło Was czym są wątki (threads) i kolejki (queues), o których otrzymaliście informację od Xcode w przypadku nieudanego wykonania jakiegoś fragmentu kodu.
OTO ŚPIESZĘ Z WYJAŚNIENIEM
Komputery (tak, iPhone i iPad to też komputery) już od jakiegoś czasu przestały przyspieszać. Tzn wciąż słyszym o procesorach taktowanych zegarami 2 i 3 gigaherce. A jeszcze kilkanaście lat temu podwojenie mocy procesorów następowało średnio raz na rok.
Brak zwiększania częstości zegara wynikł z pewnych ograniczeń fizycznych. Zamiast tego komputery otrzymały dostęp do wielu rdzeni procesorów. Znaczy to tyle, że fizycznie w procesorze jest dostępnych wiele podjednostek, które jednocześnie mogą prowadzić obliczenia.
I TU DOCHODZIMY DO WĄTKU WĄTKÓW
Żeby program mógł działać na takim procesorze, jego kod powinien być podzielony na takie fragmenty, które mogą wykonywać się równocześnie. Jakie to mogą być fragmenty? Nie trzbea długo szukać, żeby znaleźć przykłady czynności wykonywanych jednocecześnie: ś
- Ściąganie pliku podczas przeglądania strony www.
- Pobieranie piosenki podczas słuchania innej.
- Planowanie drogi ucieczki antagonisty w grze podczas ataku gracza.
- Mierzenie pulsu podczas sprawdzania godziny…
Ok. Część z tych przykładów dotyczy działania jednoczesnego różnyc aplikacji. Jednak idea pozostaje ta sama (chociaż uproszczona). Poszczególne czynności są wykonywane przez różne rdzenie procesora[1].
WĄTKI W PROGRAMOWANIU APLIKACJI DLA IOS
Jako programiści Swift możecie skorzystać z narzędzi tzw. Kolejek, które pomagają wykonywać czynności w innych wątkach. W takich kolejkach działających w tle powinniście uruchamiać wszystkie zadania, które wymagają trochę większych zasobów czasowych. Dzięki temu aplikacja będzie bardziej responsywna (czytaj: będzie działała lepiej i płynniej i więcej ludzi ją kupi).
Jak to zrobić?
Jest to bardzo proste. Kod, który ma wykonywać się w tle należy przekazać do odpowiedniej kolejki
let cQ = DispatchQueue(label: "nazwa")
Obiekt kolejki tworzony jest za pomocą konstrkutkra DispatchQueue. Taka kolejka jest domyślnie synchroniczna. To znaczy, że wszystkie operacje, które będą miały wykonywać się w niej asynchronicznie (niezależnie od głównego wątku) będą w niej wykonane po kolei.
Spróbujmy przekazać do tak stworzonej kolejki jakieś operacje. Jej działanie dobrze widać na przykładach pętli wypisujących coś na standardowe wyjście.
print("PRZED PRZEKAZANIEM ZADAŃ DO KOLEJKI")
cQ.sync {
for i in 1 ... 10 {
print("\(i) ++++")
}
}
cQ.sync {
for i in 1 ... 10 {
print("\(i) ----")
}
}
print("JUŻ PO PRZEKAZANIU ZADAŃ DO KOLEJKI")
Wywołanie synchroniczne spowodowało, że wynik jest łatwy do przewidzenia
PRZED PRZEKAZANIEM ZADAŃ DO KOLEJKI
1 ++++
2 ++++
3 ++++
4 ++++
5 ++++
1 ----
2 ----
3 ----
4 ----
5 ----
JUŻ PO PRZEKAZANIU ZADAŃ DO KOLEJKI
Widać, że kod, który jest poza nową kolejką (czyli linia wypisywana przed i po przekazaniu zadań do kolejki jest wykonana zgodnie ze spodziewaną kolejnością. Zatem główny fragment kodu czekał, aż zadania pętli się zakończą.
Jeśli zmienimy typ wywołania na async, okaże się, że zadania z pętli zaczną żyć własnym życiem, a wywołanie async nie spowoduje zablokowania głównego wątku.
PRZED PRZEKAZANIEM ZADAŃ DO KOLEJKI
1 ++++
2 ++++
JUŻ PO PRZEKAZANIU ZADAŃ DO KOLEJKI
3 ++++
4 ++++
5 ++++
1 ----
2 ----
3 ----
4 ----
5 ----
Jeszcze ciekawiej będzie, gdy pozwolimy obu zadaniom być realizowanym jednocześnie, zmieniając atrybuty nowo utworzonej kolejki.
let cQ = DispatchQueue(label: "nazwa", attributes: .concurrent)
Wówczas pętle w wywołaniach ansyhcronicznych będą przenikały się, generując taki oto wynik.
PRZED PRZEKAZANIEM ZADAŃ DO KOLEJKI
1 ----
JUŻ PO PRZEKAZANIU ZADAŃ DO KOLEJKI
1 ++++
2 ----
3 ----
4 ----
2 ++++
5 ----
3 ++++
4 ++++
5 ++++
Ale co zrobić, gdybyśmy chcieli wiedzieć, kiedy instrukcji z kolejki zakończą się? Trzeba wrócić wtedy do głównego wątku i na przykład wywołać jakąś funkcję. W treści kodu przekazanego do funkcji async dodajemy więc:
DispatchQueue.main.sync {
//Kod do wykonania w wątku głównym
}
A w bloku sync możemy już zmodyfikować UI, lub wykonać kod odpowiedzialny za zagospodarowanie wyników uzyskanych w wątku w tle.
CZY TO WSZYSTKO?
Ten przykład nie wyczerpuje możliwości wątków i kolejek, a w szczególności pomija podstawowe aspekty synchronizacji tychże. Dużo więc jeszcze jest do odkrycia! Powodzenia!