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!