Słowem wstępu
Swift udostępnia programistom trzy podstawowe typy kolekcji. Tablice, zbiory oraz mapy. Co ciekawe mapy w tym języku nazywane są słownikami. Ich wykorzystanie nie różni się znacząco od tego w innych językach. Tablice są uporządkowaną kolekcją pewnych danych. Zbiory są nieuporządkowaną kolekcją unikalnych danych. Słowniki są kolekcją nieuporządkowanych danych o postaci klucz-wartość. Co istotne tablice, zbiory oraz słowniki są w Swifcie typami generycznymi.
Domyślnie kolekcję są mutowalne. Dobrą praktyką jest jednak tworzenie kolekcji niemutowalnych (jako stałe) jeżeli mamy pewność, że nie będziemy potrzebować zmieniać ich zawartości. Dzięki temu kompilator jest w stanie zoptymalizować wydajność tworzonych przez nas kolekcji.
Tablice
Tablice przechowują dane tego samego typu w uporządkowanej kolejności. Każda pojedyncza wartość może w nich występować wiele razy na różnych pozycjach.
Przykładowo:
// Klasyczny, pełny zapis var classyArray = Array<Int>() for index in 0 ..< 10 { classyArray.append(index) } // Preferowany, krótszy zapis var shorthandArray = [Int]() for index in 0 ..< 10 { shorthandArray.append(index) } // Wydruk print(classyArray) // -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(shorthandArray) // -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Możemy również stworzyć tablicę wypełnioną określoną ilością pewnych danych domyślnych. Tworząc tablicę określamy jaki element będzie wartością domyślną oraz ile razy będzie on powtórzony.
Przykładowo:
// Tworzenie tablicy na podstawie wartości domyślnej var defaultsArray = Array(repeating: "lukaszpusz.pl", count: 3) // Tworzenie tablicy poprzez dodanie n innych tablic var defaultsArray = Array(repeating: "lukaszpusz.pl", count: 3) var newDefaultsArray = Array(repeating: "apple", count: 2) var resultArray = defaultsArray + newDefaultsArray // Inicjowanie tablicy gotowymi wartościami var items: [String] = ["Car", "MacBook"]
Na tablicach możemy wykonywać typowe operacje typu CRUD (Create, Read, Update, Delete). Sprawa jest bardzo prosta i nie odbiega schematem wykonania od przypadków znanych nam np. z Javy.
Przykładowo:
// Pobranie konkretnego elementu var defaultsArray = Array(repeating: "lukaszpusz.pl", count: 3) var myValue = defaultsArray[1] // Edytowanie konkretnego elementu defaultsArray[1] = "Blog młodego programisty" // Usunięcie konkretnego oraz ostatniego elementu defaultsArray.remove(at: 1) defaultsArray.removeLast()
Iterowanie po listach również jest analogiczne:
var defaultsArray = Array(repeating: "lukaszpusz.pl", count: 5) // Klasyczne iterowanie za pomocą pętli for item in defaultsArray { print(item) } // Iterowanie pętlą z wykorzystaniem enumerated for (index, item) in defaultsArray.enumerated(){ print("Current position = \(index). Item on current position = \(item)") }
Zbiory
Zbiory przechowują unikalne wartości tego samego typu w sposób nieuporządkowany. Innymi słowy dla takiego zbioru nie jest zdefiniowany żaden sposób, według którego kompilator miałby ustawić kolejność elementów. Zbiorów najlepiej używać w momencie gdy nie jest dla nas istotna kolejność elementów w nim występujących, a zależy nam na unikalności. Elementy, które mają zawierać się w zbiorze muszą być haszowalne (tak, brzmi to strasznie dziwnie po polsku – ang. hashable), a więc musi dać się wyliczyć ich hash, na podstawie którego kompilator jest w stanie stwierdzić unikalność każdego z elementów (poprzez porównanie). Wszystkie podstawowe typy w Swifcie są oczywiście haszowalne.
W przeciwieństwie do tablic zbiory nie mają skróconej składni (shorthand) tworzenia obiektów. Musimy korzystać z pełnej notacji. Przykładowo:
// Tworzenie zbioru i zliczenie liczby zawartych elementów var mySet = Set<Int>() print("You'll find \(mySet.count) elements in this set.") // Dodanie elementu do zbioru var mySet = Set<Int>() mySet.insert(55) print("You'll find \(mySet.count) elements in this set.") // Ponowna inicjalizacja zbioru (tym razem można użyc tylko nawiasów) mySet = [] print("You'll find \(mySet.count) elements in this set.") // Tworzenie setu gotowym zestawem danych - patrz skrócona wersja poniżej var mySet: Set<Int> = [12, 13, 14] // Jeżeli podajemy dane na starcie, kompilator sam może określić ich typ var mySet: Set = [12, 13, 14]
Tak samo jak w przypadku tablic (i słowników) możemy wykonywać operacje typu CRUD na zbiorach (pamiętając oczywiście o unikalności elementów). Przykładowo:
var nextSet: Set = [1, 2, 3] // Liczebność elementów var elementsCount = nextSet.count // Sprawdzenie czy zbiór jest pusty var emptyStatus = nextSet.isEmpty // Dodanie elementu nextSet.insert(123456) // Usunięcie elementu nextSet.remove(123456) // Usunięcie wszystkich elementów nextSet.removeAll()
W celu iterowania po zbiorze najłatwiej jest wykorzystać pętlę for-in. Przykładowo:
var numbers: Set = [1, 2, 3, 4, 5, 6] // Zwykła iteracja for number in numbers { print(number) } // Iteracja z sortowaniem (sortowanie wykorzystuje operator <) for number in numbers.sorted() { print(number) }
Na zbiorach w Swifcie można wykonywać klasyczne operacje dla teorii zbiorów:
- intersection (część wspólna)
- symmetricDifference (różnica symetryczna)
- union (suma)
- substraction (różnica)
Dobrze ilustruje to pochodząca z oficjalnej dokumentacji języka Swift poniższa ilustracja:

let oddDigits: Set = [1, 3, 5, 7, 9] let evenDigits: Set = [0, 2, 4, 6, 8] let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] oddDigits.union(evenDigits).sorted() // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] oddDigits.intersection(evenDigits).sorted() // [] oddDigits.subtracting(singleDigitPrimeNumbers).sorted() // [1, 9] oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // [1, 2, 9]
Mówiąc o zbiorach koniecznie należy wspomnieć o takich relacjach jak:
- Zbiór będący podzbiorem pewnego zbioru
- Zbiór będący nadzbiorem pewnego zbioru
- Zbiory będące rozłączne
Nie będę tłumaczył jak to działa od strony matematyki (skoro tutaj jesteś to sądzę, że wiesz o co chodzi). W języku Swift możemy sprawdzić czy te relacje faktycznie zachodzą. Przykładowo:
let a: Set = [1, 2] let b: Set = [1, 2, 3] let c: Set = [3, 4] // Wszystkie poniższe sprawdzenia zwracają true var subset = a.isSubset(of: b) var superset = b.isSuperset(of: a) var disjoint = a.isDisjoint(with: c)
Słowniki
Słowniki są typem kolekcji, w której przechowywane są pary klucz-wartość bez określonej kolejności. Każdy klucz jest unikalny i identyfikuje pewną wartość. Słowniki najlepiej wykorzystywać wszędzie tam, gdzie zależy nam na pobieraniu pewnych wartości na podstawie określonego identyfikatora.
Tak samo jak w przypadku tablic słowniki oferują nam pełny oraz skrócone zapis inicjalizacji. Pomimo faktu, że z technicznego punktu widzenia oba zapisy są całkowicie identyczne to preferowany jest krótszy z nich. Reszta operacji na słownikach jest również analogiczna w wykonaniu w stosunku do poprzednio omówionych kolekcji. Przykładowo może to wyglądać tak:
// Dłuższa wersja inicjalizacji var dictionary = Dictionary<Int, String>() // Krótsza wersja inicjalizacji var dictionary = [Int: String]() // Dodanie pary klucz-wartość do słownika // dictionaryName[key] = value dictionary[104] = "Apple" dictionary[105] = "Developer" // Słownik stanie się znowu pusty dictionary = [:] // Analogiczna dla kolekcji inicjalizacja wartościami var students: [Int: String] = [1: "Joe Doe", 2: "Max Doe", 3: "Trax Doe"] // Równoważny, krótszy zapis (znane są typy przechowywanych elementów) var students = [1: "Joe Doe", 2: "Max Doe", 3: "Trax Doe"] // Usuwanie par klucz-wartość ze słownika students[1] = nil students.removeValue(forKey: 1) // zwróci usuniętą pozycją, lub nil gdy jej nie było // Przykładowe operacje na słowniku let count = students.count let empty = students.isEmpty // Dynamiczne dodanie elementu do słownika students[17] = "Next student" // Edycja konkretnego rekordu students[2] = "Jan Kowalski"
W tym miejscu warto dodać jedną istotną uwagę co do sposobu aktualizacji wartości w słowniku. Oprócz metody przedstawionej powyżej dostępna jest również metoda updateValue(_:forKey:)
. Istotną różnicą w stosunku do wykorzystania subskryptu jest to, że zwraca ona poprzednią wartość po wykonaniu aktualizacji. Jest to sytuacja analogiczna do wykorzystania metody removeValue(_:forKey:)
. Iterowanie po słowniku również jest całkowicie analogiczne w stosunku do tablic oraz zbiorów. Przykładowo:
// Ręczne iterowanie w pętli for (key, value) in students { print("\(key): \(value)") } // Wykorzystanie metody dostępowej do zbioru kluczy słownika for key in students.keys { print(students[key]!) } // Wykorzystanie metody dostępowej do zbioru wartości słownika for value in students.values { print(value) } // Iinicjalizacja innej kolekcji za pomocą zbiorów słownika let studentsIds = [Int](students.keys) let studentsNames = [String](students.values)
Tak samo jak w przypadku zbiorów słowniki nie posiadają odgórnie określonej kolejności elementów. Chcąc mieć dostęp do posortowanego słownika można skorzystać z metody znanej nam już ze zbiorów (musimy jednak wprowadzić regułę, która będzie mówić kompilatorowi w jaki sposób ma posortować zawartość słownika):
for (key, value) in students.sorted(by: {$0.0 < $1.0}) { print("\(key) = \(value)") }
Warto jednak zaznaczyć, że sortowanie elementów tych kolekcji jest iluzoryczne ponieważ metoda sorted()
zwraca tak naprawdę tablicę elementów!
Źródła:
- https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html
- https://stackoverflow.com/questions/25377177/sort-dictionary-by-keys