Advertisement
  1. Code
  2. Swift

Wprowadzenie do języka Swift — część pierwsza

Scroll to top
Read Time: 12 min

Polish (Polski) translation by Agnieszka Górczyńska (you can also view the original English article)

Na konferencji WWDC w 2014 roku firma Apple przedstawiła jedną z największych od roku 2008, z programistycznego punktu widzenia, aktualizacji systemu iOS.  W nowej wersji systemu wprowadzono kolejne frameworki, między innymi HomeKit, HealthKit, CloudKit oraz obsługę rozszerzeń. Jednak największą niespodzianką wspomnianej konferencji okazała się prezentacja całkowicie nowego języka programowania — Swift.

Język programowania Swift został od podstaw opracowany z myślą o zapewnieniu jak największej wydajności i bezpieczeństwa. Opiera się na tych samych API, które znajdziesz Objective-C. Innymi słowy, jeśli potrafisz coś zrobić w Objective-C, bez problemu zrobisz to samo w Swifcie. Wprowadzono kilka nowych koncepcji, które szczególnie docenią doświadczeni programiści. Do wspomnianych koncepcji z pewnością powrócę w kolejnych artykułach poświęconych językowi Swift.

W moich artykułach przyjąłem założenie, że Czytelnik zna już język Objective-C. W pierwszym artykule z tej serii przedstawiam główne założenia języka Swift, strukturę plików oraz składnię. W drugim artykule przybliżę zaawansowane aspekty składni Swift’a, takie jak typ danych optionals i zarządzanie pamięcią. Zatem czapki z głów — przygotujcie się na nadejście nowego języka programowania!

1. Filozofia

Aby ułatwić poznanie języka Swift, na przestrzeni kilku ostatnich lat firma Apple wprowadziła wiele strukturalnych udogodnień w Objecive-C. Usprawnienia takie jak bloki, literały tablic i słowników oraz mechanizm ARC (ang. Automatic Reference Counting, czyli automatyczne zliczanie odwołań) to jedne z wielu elementów dodanych do Objective-C i ułatwiających przejście do języka Swift.

Jednym z najważniejszych filarów całej filozofii języka Swift jest kod inicjalizacyjny. Wszystkie obiekty i zmienne definiowane w tym języku muszą być zainicjalizowane w kodzie. Każdy niezainicjalizowany obiekt lub zmienna zaowocuje błędem w trakcie kompilacji. Inicjalizacja gwarantuje, że obiekt lub zmienna zawsze ma wartość. Istnieje jednak wyjątek, gdy wartość początkowa nie może zostać zdefiniowana. W tym przypadku zmienna nazywana jest optional. Jednak do tego tematu powrócimy w drugim artykule tej serii.

Kolejnym istotnym filarem założeń tego języka programowania jest kwestia kompletności fragmentu kodu. Wszelkie konstrukcje warunkowe, takie jak if lub switch-case muszą uwzględniać wszystkie warunki.  Innymi słowy, nie można pominąć żadnej ścieżki wykonywania kodu. Pominięcie którejkolwiek ścieżki w konstrukcji warunkowej spowoduje wygenerowanie błędu w trakcie kompilacji.

Ostatni istotny element założeń języka programowania Swift to preferowanie stałych zamiast zmiennych. W Swifcie zmienne i stałe definiujemy w następujący sposób:

1
let someConstant : String = "This is a constant"
2
var someVariable : String = "This is a variable"

W powyższym przykładzie słowo kluczowe let jest używane do zdefiniowania stałej, podczas gdy za pomocą słowa kluczowego var definiujemy zmienną. Przez takie ułatwienie w zakresie definiowania stałych, Apple zachęca do ich użycia, o ile to możliwe. W ten sposób otrzymujemy bezpieczniejszy kod wykonywany w środowisku wielowątkowym. Ponadto zapewniamy lepszą optymalizację kodu, ponieważ kompilator wie, że wartość stałej nie ulegnie zmianie.

Swift to o wiele więcej niż tylko kilka usprawnień składni i formatowania. Język został od podstaw opracowany z myślą o usunięciu wielu źródeł błędów najczęściej występującychw językach C, C++, a tym samym także w Objective-C. Usprawnia wprowadzono na wielu obszarach:

  • próby użycia nieistniejących elementów tablicy;
  • niezainicjalizowane dane;
  • niesprawdzone typy wartości zwrotnych;
  • niesprawdzone wskaźniki dostępu;
  • niejawne przejścia do kolejnych poleceń;
  • błędy typu goto.

Jako programista tworzący kod na platformach zarówno iOS, jak i Android, niejako z pierwszej ręki wiem, o ile więcej przyjemności dostarcza tworzenie kodu na platformie iOS z użyciem Cocoa i UIKit. Z tej serii artykułów przekonasz się, o ile więcej radości dostarcza tworzenie kodu w języku Swift na platformie iOS.

2. Struktura pliku

W Objective-C istnieją pliki nagłówkowe(.h) i implementacji (.m).  To jest cecha odziedziczona przez Objective-C po języku C.

W języku Swift klasa definiowana jest za pomocą pojedynczego pliku implementacji (.swift) zawierającego wszystkie niezbędne definicje i implementację klasy. Takie rozwiązanie jest podobne do stosowanych w innych językach programowania, na przykład Javie lub C#.

Nie ma już potrzeby korzystania z plików nagłówkowych oraz dodawania irytującego polecenia #IFNDEF na początku plików nagłówkowych.

3. Składnia

Pierwszą rzeczą zwracającą uwagę w języku Swift jest brak średnika na końcu każdego polecenia. Z uwagi na to, że każdy wiersz sam w sobie stanowi polecenie, więc nie trzeba wstawiać średnika na jego końcu.

Podkreśliłem wyrażenie nie trzeba, ponieważ tak naprawdę nic nie może programisty powstrzymać przed umieszczeniem średnika na końcu polecenia. Osobiście dodaję średniki na końcu poleceń tylko wtedy, gdy uważam, że zwiększa to czytelność polecenia. W każdym razie zdaję sobie sprawę, jak trudno pozbyć się tego nawyku, szczególnie programistom od lat tworzącym kod z użyciem frameworka Cocoa.

Inną istotną zmianą, którą wprowadzono w języku Swift polega na tym, że użycie nawiasów klamrowych w konstrukcjach if jest obowiązkowe. W praktyce oznacza to pozbycie się błędów typu Heartbleed.

Składnia jest naprawdę bardzo złożonym tematem. Swift zawiera w sobie wiele niuansów i subtelności wymagających szczegółowego omówienia, co jednak nie jest tematem tego opracowania. Skoncentruję się jedynie na zmianach zauważalnych dla programisty Objective-C.

4. Podobieństwa do Objective-C

Rozpocznę od zaprezentowania trzech fragmentów kodu ilustrujących pewne podobieństwa do Objective-C. To powinno Ci pomóc w lepszym zrozumieniu, czym jest język Swift.

1
// Objective-C

2
for (int index = 0; index < 5; i++) {
3
     NSLog(@"%d",index);
4
}
5
6
// Swift

7
for index in 1..<5 {
8
    plrintln("\(index)");
9
}
1
// Objective-C

2
switch(index) {
3
    case 0:
4
        break;
5
    case 1:
6
        break;
7
    default:
8
        break;
9
}
10
11
// Swift

12
switch(index) {
13
    case 0:
14
    
15
    case 1:
16
17
    default:
18
}
19
20
// no break statement
1
// Objective-C

2
if (index == 0) {
3
4
}
5
6
// Swift

7
if index == 0 {
8
9
}
10
11
// parentheses are optional

12
// curly braces are required

Programiści Objective-C z pewnością zauważą, że Swift ma takie same polecenia warunkowe i iteracji, na przykład if-else, pętle for, pętle for-in i konstrukcje switch.

Język Swift oferuje dwa rodzaje operatorów zakresu ..< i ... służących do określenia zakresu wartości. W przedstawionej powyżej pętli for używamy półzamkniętego operatora zakresu (...<) służącego do określenia wartości 1, 2, 3, 4 z wyłączeniem 5. Innym rodzajem operatora zakresu jest zamknięty operator zakresu (...). Określa on zakres wartości znajdujących się po obu jego stronach. Przykładem wymienionego operatora jest 1...5. Oznacza on zakres wartości od 1 do 5, włączając 5.

5.  Definiowanie zmiennych i stałych

Spójrz raz jeszcze na przedstawiony wcześniej fragment kodu.

1
let someConstant : String = "This is a constant";
2
var someVariable : String = "This is a variable";

W języku Swift stałe definiujemy za pomocą słowa kluczowego let, natomiast zmienne za pomocą var. Dwukropek (:) jest znakiem określającym typ. W powyższym przykładzie tworzymy stałą i zmienną typu String.

Ponadto inicjalizujemy stałą i zmienną za pomocą ciągu tekstowego. W języku Swift ciągi tekstowe są określane w podobny sposób, jak ciągi tekstowe C w Objective-C, nie są poprzedzane znakiem @.

Objective-C to język ściśle określonych typów, co oznacza, że zawsze trzeba podać typ zmiennej lub parametru. W języku Swift jest podobnie, ale działanie kompilatora jest sprytniejsze, ponieważ potrafi on określić typ zmiennej. Ponadto kompilator gwarantuje, że nie wystąpi żadne niewłaściwe rzutowanie zmiennej bez wyraźnego polecenia programisty.

Jeśli zatem zmodyfikujemy pokazany wcześniej przykład i tym razem zastosujemy określanie typu przez kompilator, wtedy kod będzie miał następującą postać:

1
let someConstant = "This is a constant";
2
var someVariable = "This is a variable";
3
let someInt = 1;
4
let someFloat = 1.0;

Teraz kod wygląda dużo lepiej i przejrzyściej. Kompilator jest w stanie ustalić, że zmienna someInt jest typu Int, natomiast someFloat jest typu Double.

6. Ciągi tekstowe

Jednym ze sposobów przekonania się o sile i skuteczności języka programowania jest sprawdzenie, w jaki sposób są przeprowadzane operacje na ciągach tekstowych. Objective-C oferuje bardzo wiele funkcji umożliwiających wykonywanie tego rodzaju operacji. Wprawdzie wspomniane funkcje są dużo lepsze niż w innych językach, ale czasem bywają zbyt rozwlekłe i niejasne.

Spójrzmy na przykład w Objective-C. W celu połączenia dwóch ciągów tekstowych w Objective-C należy wykonać następującą operację:

1
NSString *string = @"Hello ";
2
NSString *greeting = [string stringByAppendingString:@"World!"];

Z kolei w języku Swift, aby połączyć ze sobą dwa ciągi tekstowe konieczne jest jedynie użycie operatora +: To naprawdę jest takie proste.

1
let string = "Hello "
2
let greeting = string + "World!"

Ciągi tekstowe w języku Swift są kodowane jako Unicode, co oznacza możliwość zapisywania ichw następujący sposób:

1
let string = "你好世界"

Do iteracji znaków ciągu tekstowego używamy polecenia for-in, jak pokazano w poniższym przykładzie. Wymienioną pętlę można stosować także go iteracji ciągów tekstowych Unicode. To naprawdę świetne rozwiązanie.

1
let str = "Hello";
2
for char in str {
3
    println(char);
4
}
5
6
// outputs
7
// H
8
// e
9
// l
10
// l
11
// o

Ostatnią kwestią związaną z ciągami tekstowymi, którą chciałbym omówić w tym artykule to interpolacja. Jeżeli ciąg tekstowy w Objective-C ma zawierać wartości zmiennych, to używamy wywołania [NSString stringWithFormat:]. W języku Swift zmienne mogą być osadzone. Przyjrzyjmy się poniższemu przykładowi:

1
let x = 4;
2
let y = 5;
3
4
println( "\(x) x \(y) = \(x*y)")
5
6
// outputs
7
// 4 x 5 = 20

W celu użycia interpolacji ciągów tekstowych należy zmienną lub wywołanie funkcji umieścić w nawiasie okrągłym i poprzedzić nawias ukośnikiem, na przykład \(wyrażenie).

7. Tablice i słowniki

Słowa kluczowe Array i Dictionary

Programistom Objective-C z pewnością bliski jest temat tablic i słowników. Język Swift także posiada klasy kolekcji, które jednak jeszcze poszerza o dodatkowe możliwości.

W jaki sposób nazwane są kolekcje w języku Swift? Tutaj są one określane mianem tablicy (Array) i słownika (Dictionary). Deklarowanie tablicy w języku Swift odbywa się podobne jak Objective-C, czyli przy użyciu nawiasów kwadratowych ([]), ale bez znaku @ przed nawiasem.

1
let someArray:[String] = ["one","two","three"];
2
var someOtherArray:[String] = ["alpha","beta","gamma"];

Podobnie jest w przypadku słowników. Jednak tutaj zamiast nawiasów klamrowych używamy nawiasów kwadratowych.

1
let someDictionary:[String:Int] = ["one":1, "two":2, "three":3];
2
var someOtherDictionary:[String:Int] = ["cats":1, "dogs":4, "mice":3];

Zmienność

Jeśli obiekt Array jest odpowiednikiem NSArray, a Dictionary jest odpowiednikiem NSDictionary, to w jaki sposób można utworzyć w języku Swift modyfikowalne tabele i słowniki?

Odpowiedź jest bardzo prosta — deklarując obiekt jako zmienną. Innymi słowy, w poprzednim przykładzie someArray odpowiada egzemplarzowi NSArray, natomiast someOtherArray to odpowiednik egzemplarza NSMutableArray. To samo dotyczy także słowników. Dlatego też w powyższym przykładzie someDictionary odpowiada egzemplarzowu NSDictionary, natomiast someOtherDictionary odpowiada NSMutableDictionary. Całkiem zgrabnie, prawda?

W Objective-C tablice mogą zawierać jedynie obiekty, natomiast w języku Swift kolekcje zawierają zarówno obiekty, jak i proste typy danych, na przykład liczby całkowite i zmiennoprzecinkowe. Kolejną istotną różnicą w stosunku do Objective-C jest fakt, że kolekcje w języku Swift mają zdefiniowany typ – wyraźnie wskazany przez programistę lub ustalany przez kompilator w czasie kompilacji. Dzięki określeniu typu obiektów w kolekcji, Swift zapewnia im większe bezpieczeństwo.

Nawet jeśli można pominąć typ zmiennej podczas jej deklarowania, nie zmienia to jednak faktu, że kompilator przypisze typy obiektom w kolekcji. Użycie interferencji typu ułatwia zachowanie czytelności i przejrzystości kodu.

Możemy ponownie zdefiniować zadeklarowane wcześniej obiekty Array i Dictionary w następujący sposób:

1
let someArray = ["one","two","three"];
2
var someOtherArray = ["alpha","beta","gamma"];
3
let someDictionary = ["one":1, "two":2, "three":3];
4
var someOtherDictionary = ["cats":1, "dogs":4, "mice":3];

Kompilator sprawdzi kolekcje w trakcie ich inicjalizacji i wybierze właściwy typ. Innymi słowy rozpozna, że someArray i someOtherArray to kolekcja obiektów typu String, z kolei someDictionary i someOtherDictionary to słowniki, w których klucze są typu String, natomiast wartości typu Int.

Operacje na kolekcjach

Dodawanie obiektu lub innej tabeli do już istniejącej jest podobne do operacji łączenia ciągów tekstowych i także opiera się na użyciu operatora +.

1
var array = ["one","two","three"]; // mutable array
2
array += "four"; // add element to array
3
array += ["five","six"]; // add array to array

Podobnie przedstawiają się operacje na słownikach.

1
var dictionary = ["cat": 2,"dog":4,"snake":8]; // mutable dictionary
2
dictionary["lion"] = 7; // add element to dictionary
3
dictionary += ["bear":1,"mouse":6]; // add dictionary to dictionary

Typy kolekcji

Wcześniej wspomniałem już, że w języku Swift kolekcje mają określony typ. Mógłbyś zapytać, co to jednak dokładnie oznacza?  Jeśli określimy, że kolekcja zawiera obiekty typu String, wówczas do tej kolekcji możemy dodawać jedynie obiekty String. Dodanie obiektów innego typu spowoduje wystąpienie błędu.

Spójrzmy na przykład, który powinien rozjaśnić tę kwestię. Definiujemy kolekcję obiektów Car.  Poniższy fragment kodu pokazuje definicję klasy Car, a modyfikowalna tablica cars zawiera trzy egzemplarze Car.

1
// Car class
2
class Car {
3
    var speed = 0.0
4
    
5
    func accelerate(by: Double = 1.0) -> Bool {
6
        speed += by;
7
        return true;
8
    }
9
}
10
11
var cars = [ Car(), Car(), Car()];

W tle kompilator ustali typ tablicy. Jeśli chcemy dodać do kolekcji nowyegzemplarz Car, możemy po prostu użyć operatora +, jak pokazano poniżej:

1
cars += Car();

Jednak próba dodania obiektu innego typu spowoduje wystąpienie błędu:

1
cars += "Some String"; // this will cause a compiler error

Korzyścią przyjętego rozwiązania jest większe bezpieczeństwo typu podczas pobierania obiektów z kolekcji. Wyeliminowana została także konieczność rzutowania kolekcji obiektów przed ich użyciem.

W naszym przykładzie obiekt Car ma metodę accelerate(). Ponieważ kolekcja ma określony typ, to w jednym wierszu kodu możemy pobrać element z tablicy i natychmiast wywołać wymaganą metodę. Nie musimy przejmować się typem elementu, ponieważ kolekcja zawiera jedynie obiekty Car.

1
cars[0].accelerate(by: 2.0);

Aby wykonać tę samą operację w Objective-C i jednocześnie zapewnić ten sam poziom bezpieczeństwa, musielibyśmy użyć następującego fragmentu kodu:

1
id pointer = cars[0];
2
if([pointer isKindOfClass:[Car class]]) {
3
    Car* car = (Car*) pointer;
4
    [car accelerateBy:2.0];
5
}

Na koniec do przeprowadzenia iteracji przez tablicę używamy pętli for-in:

1
for car in cars {
2
    car.accelerate(by:2.0);
3
}

Do do przeprowadzenia iteracji przez słownik również użyjemy pętli for-in:

1
for (key,value) in someDictionary {
2
    println("Key \(key) has value \(value)"
3
}

Jak możesz zobaczyć kolekcje o ściśle określonym typie są w języku Swift niezwykle istotnym elementem, który oferuje potężne możliwości.

Zakończenie

Z powyższego artykułu można się całkiem sporo dowiedzieć o języku Swift. Na pewno potrzeba nieco czasu, aby w pełni nabrać wprawy w jego użyciu. Zachęcam Cię do jak najszybszej instalacji Xcode 6, a następnie za pomocą nowych plików typu Playground wypróbowanie przykładów przedstawionych w artykule. Dzięki plikom typu Playground możesz w czasie rzeczywistym wypróbowywać polecenia języka Swift.

I to na tyle w tym artykule. W następnym z serii zajmiemy się krotkami, funkcjami, domknięciami, klasami i typem optional. Ponadto dowiesz się, jak zarządzać pamięcią w języku Swift. A więc do zobaczenia!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.