Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Swift

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

by
Difficulty:BeginnerLength:LongLanguages:

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

W poprzednim artykule poświęconym wprowadzeniu do języka programowania Swift skoncentrowałem się głównie na założeniach języka, przedstawiłem pokrótce jego składnię oraz wyszczególniłem kilka podstawowych różnic w stosunku do Objective-C. W tym artykule będę kontynuował omawianie składni języka Swift. Ponadto poruszę temat optionals oraz zarządzania pamięcią.

1. Konstrukcje warunkowe i pętle

Polecenie if

Polecenie if w języku Swift jest niemal identyczne z istniejącym w Objective-C. W stosunku do Objective-C występują tylko dwie subtelne różnice:

  • nawiasy okrągłe wokół zmiennej warunku są opcjonalne;
  • nawiasy klamrowe są wymagane.

Powyższe punkty to jedyne różnice między poleceniami if w obu wymienionych językach.

Zakresy

Jak zobaczyłeś w poprzednim artykule, Swift oferuje dwa rodzaje operatorów (..< i ...) przeznaczonych do definiowania zakresu wartości. Pierwszy z nich nosi nazwę pół-zamkniętego operatora zakresu, natomiast drugi zamkniętego operatora zakresu.

Pół-zamknięty operator zakresu, na przykład 1..<5 przedstawia wartości 1, 2, 3, i 4 (nie obejmuje wartości 5). Z kolei zamknięty operator zakresu, taki jak 1...5 przedstawia wartości 1, 2, 3, 4 i tutaj obejmuje 5.

Zakresy mogą zostać użyte w pętlach for , tablicach (array), a nawet w konstrukcjach switch.  Spójrz na przedstawione poniżej przykłady.

Konstrukcja switch

Konstrukcja switch ma znacznie większe możliwości w języku Swift niż w Objective-C. W Objective-C wynikiem wyrażenia polecenia switch musi być liczba całkowita, a wartości poszczególnych poleceń typu case powinny być stałą. Natomiast w przypadku języka Swift sprawa przedstawia się inaczej. Polecenia case mogą być dowolnego typu włączając w to zakresy.

W języku Swift konstrukcja switch nie posiada poleceń break i nie następuje automatyczne przejście od jednego bloku case do następnego. Tworząc polecenie switch należy szczególną uwagę zwrócić na to, aby wszystkie możliwe warunki zostały spełnione przez bloki case.  Zaniedbanie tego wymogu skutkuje powstaniem błędu w trakcie kompilacji. Najpewniejszym sposobem uwzględnienia wszystkich warunków jest dołączenie bloku default.

Poniżej przedstawiono przykład konstrukcji switch zawierającej bloki case typu String:

W języku Swift nie następuje domyślne przejście do następnego polecenia case po wykonaniu poprzedniego. To jest celowa decyzja projektowa, która ma pomóc programistom w unikaniu często popełnianych błędów. Jeśli dane polecenie case ma być wykonane, wówczas można użyć słowa kluczowego fallthrough w celu wskazania tego faktu kompilatorowi.

Na tym jednak nie poprzestajemy. W języku Swift pojawiły się jeszcze dwie inne funkcje konstrukcji switch: dołączanie wartości i klauzula where. Wartości są dołączane za pomocą słów kluczowych caselet umożliwiających przyporządkowanie stałej do dopasowanego polecenia case. Z kolei klauzula where umożliwia dodawanie za pomocą słowa kluczowego where specjalnego warunku do polecenia case.

Te dwie koncepcje najlepiej można wyjaśnić za pomocą przykładów. Poniższy blok kodu pokazuje, w jaki sposób działa dołączanie wartości.

W powyższym fragmencie kodu pierwsze polecenie case - case (let x, 0) - dopasowuje wartości, gdzie xaxis ma dowolną wartość, natomiast yaxis ma wartość 0. Ponadto dołączamy xaxis do stałej x w poleceniu case.

Poniżej został zaprezentowany przykład praktycznego użycia klauzuli where.

2. Funkcje i domknięcia

Funkcje

W języku Swift definiowanie funkcji i domknięć jest bardzo proste. Programiści, którzy mają doświadczenie w tworzeniu kodu w Objective-C znają te pojęcia jako funkcje i bloki.

W przypadku Swifta parametry funkcji mogą posiadać wartości domyślne, co przypomina nieco języki skryptowe, takie jak PHP i Ruby.

Składnia funkcji przedstawia się następująco:

Poniżej przedstawiono kod funkcji sayHello() przyjmującej parametr name typu String. Wartością zwrotną funkcji jest wartość boolowska (Bool).

W celu przekazania wartości domyślnej dla parametru name implementacja funkcji może przedstawiać się następująco:

Elementem, który nie występuje w Objective-C jest krotka. W języku Swift funkcje mogą zwracać wiele wartości w formie krotki. Wspomniana krotka jest traktowana jako pojedyncza zmienna, co oznacza możliwość przekazania jej w dokładnie taki sam sposób, jak pojedynczej zmiennej.

Krotka jest bardzo prosta w użyciu. Właściwie omówiłem już jej działanie w poprzednim artykule przy okazji omawiania słowników. W poniższym fragmencie kodu, para klucz-wartość jest krotką.

Być może zastanawiasz się, jak używać krotki i jakie korzyści można osiągnąć z jej stosowania? Spójrzmy na inny przykład. Zmodyfikujemy powyższą funkcję sayHello() w taki sposób, że gdy wartością zwrotną jest true, wtedy zostanie do niej dołączony komunikat. W tym celu wykorzystamy krotkę (Bool, String). Zmodyfikowana funkcja sayHello() przedstawia się następująco: 

Już od dłuższego czasu krotka znajdowała się na liście życzeń wielu programistów tworzących kod w Objective-C.

Kolejną bardzo istotną korzyścią wynikającą z użycia krotki jest możliwość nazwania zwracanych zmiennych. Jeśli w poprzednim przykładzie nadamy nazwy zmiennym w krotce, wówczas kod funkcji będzie miał następującą postać:

Oznacza to, że zamiast definiowania oddzielnych stałych dla poszczególnych elementów krotki, dostęp do nich możemy uzyskać za pomocą notacji z kropką, na przykład status.success i status.greeting.

Domknięcia

Domknięcia w języku Swift są tym samym, czym bloki w Objective-C. Mogą być definiowane w miejscu ich wykonania, przekazane jako parametr lub zwrócone przez funkcje. Używamy ich dokładnie w taki sam sposób, jak bloki w Objective-C.

Również definiowanie domknięć jest proste. Tak naprawdę funkcja jest szczególnym przypadkiem domknięcia. Nie dziwi więc fakt, że definiowanie domknięcia przypomina definiowanie funkcji.

Domknięcia są typem pierwszej klasy, co oznacza, że mogą być przekazywane i zwracane przez funkcje dokładnie tak samo, jak każdy inny typ, na przykład: Int, String, Bool itd. Domknięcia są w gruncie rzeczy blokami kodu, można je późniejwywoływać i mieć dostęp do zakresu, w którym zostały zdefiniowane.

Utworzenie nienazwanego domknięcia jest proste, i sprowadza się do ujęcia bloku kodu w nawias klamrowy. Parametry i typ wartości zwrotnej domknięcia są oddzielone słowem kluczowym in od części głównej domknięcia.

Przyjmujemy założenie, że chcemy zdefiniować domknięcie o wartości zwrotnej true, jeżeli liczba będzie parzysta. Tego rodzaju domknięcie mogłoby przedstawiać się następująco:

Domknięcie isEven przyjmuje pojedynczy parametr w postaci wartości typu Int. Natomiast wartością zwrotną domknięcia jest wartość boolowska (Bool). Typ omawianego domknięcia to (number: Int) - > Bool, lub w skrócie (Int - > Bool). Domknięcie isEven możemy wywołać w dowolnym miejscu kodu, podobnie jak blok kodu w Objective-C.

Aby tego rodzaju domknięcie mogło być przekazane jako parametr funkcji, jego typ trzeba podać w definicji funkcji:

W powyższym przykładzie parametr verifier funkcji verifyIfEven() jest domknięciem przekazanym funkcji.

3. Klasy i struktury

Klasy

Przechodzimy teraz do kamienia węgielnego programowania zorientowanego obiektowo, czyli do klas. Jak wcześniej wspomniano, klasy są definiowane w pojedynczym pliku implementacji o rozszerzeniu .swift. Ten plik zawiera także deklaracje i metody.

Utworzenie klasy następuje za pomocą słowa kluczowego class, po którym należy podać nazwę klasy. Implementacja klasa znajduje się wewnątrz nawiasu klamrowego. Podobnie jak w Objective-C, także w języku Swift nazwy klasy stosują styl CamelCase i rozpoczynają się dużą literą.

W celu utworzenia egzemplarza klasy Hotel należy wydać poniższe polecenie:

W języku Swift nie ma konieczności wywoływania metody init() w obiektach, ponieważ wymieniona metoda jest wywoływana automatycznie.

Dziedziczenie klas podlega takiemu samemu wzorcowi, jak zastosowany w Objective-C. Do rozdzielenia nazw klas definiowanej i nadrzędnej używany jest dwukropek. W poniższym przykładzie klasa Hotel dziedziczy po klasie BigHotel.

Podobnie jak w Objective-C, w celu uzyskania dostępu do właściwości obiektu wykorzystywana jest składnia z kropką. Jednak Swift używa składni kropki także do wywoływania klas i metod egzemplarza, o czym możesz się przekonać analizując poniższy fragment kodu:

Właściwości

Kolejna różnica między językami Objective-C i Swift wiąże się z faktem, że drugi z wymienionych nie rozróżnia zmiennych egzemplarza (tak zwanych ivars) od właściwości. Zmienna egzemplarza jest traktowana jako właściwość.

Deklarowanie właściwości odbywa się w dokładnie taki sam sposób, jak definiowanie zmiennej lub stałej, czyli za pomocą słówkluczowych odpowiednio var i let. Jedyna różnica dotyczy kontekstu, w którym zostały zdefiniowane – tutaj to będzie kontekst klasy.

W powyższym przykładzie rooms to niemodyfikowalna stała, której przypisano wartość 10, natomiast fullRooms to zmienna o wartości początkowej wynoszącej 0. Wartość zmiennej można później zmienić. Regułą jest, że właściwości muszą być zainicjowane w chwili ich deklarowania. Jedyny wyjątek od tej reguły dotyczy optionals, czym się zajmiemy w dalszej części artykułu.

Obliczane właściwości

W języku Swift znajdziemy również tak zwane obliczane właściwości. Można je uznać za po prostu inny, nieprzechowujący wartości rodzaj metod typu getter i setter. Jak sama nazwa wskazuje, obliczenie wartości następuje w locie.

Poniżej przedstawiono przykład obliczanej właściwości. Dla pozostałych przykładów właściwość rooms została zmieniona na deklarowaną za pomocą słowa kluczowego var. Powód tej zmiany stanie się jasny w dalszej części artykułu.

Ponieważ właściwość description jest typu tylko do odczytu i zawiera tylko polecenie return, więc można pominąć słowo kluczowe get i nawiasy klamrowe, pozostawiając jedynie polecenie return. To jest rodzaj skrótu, z którego będziemy korzystać do końca tego artykułu.

Istnieje również możliwość zdefiniowania obliczanych właściwości, które będą w trybie odczytu i zapisu. Chcemy, aby w naszej przykładowej klasie Hotel właściwość emptyRooms pobierała liczbę wolnych pokoi w hotelu. Jednocześnie po przypisaniu wartości właściwości emptyRooms powinno następować uaktualnienie fullRooms. Do tego celu możemy wykorzystać słowo kluczowe set, jak w poniższym fragmencie kodu:

W setterze emptyRooms stała newValue jest podawana i przedstawia wartość przekazaną setterowi. Trzeba koniecznie zapamiętać, że obliczane właściwości zawsze są deklarowane jako zmienne za pomocą słowa kluczowego var, ponieważ ich obliczana wartość może ulec zmianie.

Metody

We wcześniejszej części artykułu zostały omówione funkcje. Metoda to po prostu funkcja, ale powiązana z typem, na przykład z klasą. W poniższym fragmencie kodu implementujemy metodę egzemplarza o nazwie bookNumberOfRooms() w klasie Hotel, którą utworzyliśmy wcześniej.

Funkcja inicjalizująca

Domyślna funkcja inicjująca klasę to init(). W wymienionej funkcji następuje zdefiniowanie wartości początkowych tworzonego egzemplarza.

Na przykład, jeżeli potrzebujemy egzemplarz klasy Hotel wraz z setką pokoi, wówczas funkcja inicjująca musi przypisać właściwości rooms wartość 100. Pamiętasz, że wcześniej zmieniliśmy w klasie Hotel typ komponentu rooms ze stałej na zmienną? Po prostu w klasach potomnych nie można zmieniać wartości odziedziczonych stałych, zmodyfikowane mogą być tylko dziedziczone zmienne.

Funkcja inicjująca może pobierać parametry. Przykład tego rodzaju rozwiązania przedstawiono w poniższym fragmencie kodu:

Nadpisywanie metod i obliczonych właściwości

To jest jedna z najbardziej interesujących cech języka Swift. W klasie potomnej można nadpisywać zarówno metody, jak i obliczone właściwości. W tym celu wykorzystujemy słowo kluczowe override. Teraz w klasie CustomHotel nadpiszemy obliczoną właściwość description:

Wskutek wprowadzonych zmian, wartością zwracaną przez właściwość description będzie wynik wywołania metody description w klasie nadrzędnej i dołączenie do niego ciągu tekstowego Witaj!.

Najciekawszym aspektem podczas nadpisywania metod i obliczonych właściwości jest słowo kluczowe override. Kiedy kompilator napotka słowo kluczowe override, wtedy sprawdza, czy klasa nadrzędna implementuje nadpisywaną metodę. Ponadto kompilator sprawdza, czy właściwości i metody klasy nie powodują konfliktów z właściwościami lub metodami znajdującymi się wyżej w drzewie dziedziczenia.

Nie jestem w stanie powiedzieć, ile razy zwykła literówka w nadpisywanej metodzie w Objective-C doprowadzała mnie do szału, ponieważ kod nie działał. W języku Swift, w tego rodzaju sytuacjach kompilator dokładnie wskaże błąd.

Struktury

Struktury są definiowane za pomocą słowa kluczowegostruct, a ich możliwości znacznie większe niż w językach C i Objective-C. W języku C struktura może zdefiniować jedynie wartości i wskaźniki. Z kolei struktury w Swifcie są podobne do struktur znanych z C, ale obsługują także metody i obliczone właściwości.

Wszystko to, co można zrobić za pomocą klasy jest możliwe także do zrobienia za pomocą struktury. Istnieją tylko dwie ważne różnice:

  • w przeciwieństwie do klas, struktury nie obsługują dziedziczenia;
  • struktury są przekazywane poprzez wartość, podczas gdy klasy przez referencję.

Poniżej pokazano przykłady kilku struktur w języku Swift:

4. Optionals

Rozwiązanie problemu

Jeżeli wcześniej programowałeś tylko w Objective-C, wówczas koncepcja optionals będzie dla Ciebie zupełnie nowa. Pozwala na rozwiązanie problemu, przed którym wcześniej lub później stanie każdy programista. Kiedy uzyskujemy dostęp do zmiennej o nieznanej wartości, zwykle zwracamy tak zwaną wartość sentinel wskazującą brak wartości. Poniżej przedstawiono ilustrację tej koncepcji w Objective-C:

W powyższym fragmencie kodu próbujemy ustalić położenie litery B w ciągu tekstowym someString. Jeżeli litera B zostanie znaleziona, jej położenie będzie przechowywane w zmiennej pos. Co się stanie w sytuacji, gdy ciąg tekstowy someString nie zawiera litery B?

Według dokumentacji Objective-C, wartością zwrotną rangeOfString: będzie NSRange wraz z location o wartości w postaci stałej NSNotFound. W przypadku rangeOfString: wartość typu sentinel to NSNotFound. Wspomniana wartość typu sentinel jest używana do wskazania, że wartość zwrotna jest niepoprawna.

W Cocoa mamy wiele przykładów użycia tej koncepcji,ale same wartości typu sentinel są różne w zależności od kontekstu: 0, -1, NULL, NSIntegerMax, INT_MAX, Nil itd. Problem programisty wiąże się z koniecznością zapamiętania, które wartości typu sentinel muszą być stosowane w poszczególnych kontekstach. Jeżeli programista nie zachowa szczególnej ostrożności, poprawną wartość może uznać za sentinel i na odwrót. Swift rozwiązuje ten problem za pomocą tak zwanych optionals. Cytując Briana Laniera: „optionals to jedna wartość typu sentinel możliwa do stosowania we wszystkich kontekstach”.

Optionals ma dwa stany. Pierwszy to nil oznaczający, że optional nie ma wartości. Natomiast drugi stan wskazuje na przechowywanie poprawnej wartości. Optionals możesz potraktować jak pakiet wraz ze wskaźnikiem informującym, czy zawartość pakietu jest prawidłowa.

Sposób użycia

Wszystkie typy w języku Swift mogą być optional. Zdefiniowanie optional odbywa się przez umieszczenie znaku zapytania (?) po deklaracji, na przykład:

Przypisanie wartości pakietowi optional następuje w dokładnie taki sam sposób, jak w przypadku stałych i zmiennych:

Jak wspomniano optional przypomina pakiet. Polecenie let someInt: Int? powoduje zdefiniowanie pustego pudełka wraz z wartością nil. Po przypisaniu wartości 10 do optional, pudełko zawiera liczbę całkowitą 10, a stan przyjmuje postać inna niż nil.

W celu pobrania zawartości optional używamy operator ! . Przed rozpakowaniem optional trzeba się upewnić, że przechowuje poprawną wartość. W przeciwnym razie nastąpi wygenerowanie błędu w trakcie działania aplikacji. Poniżej pokazano przykład uzyskania dostępu do wartości przechowywanej w optional:

Powyższy wzorzec jest na tyle często stosowany wjęzyku Swift, że ten blok kodu można uprościć przez użycie dołączania optional za pomocą słów kluczowych if let. Spójrz na uaktualnioną wersję kodu:

Optional to jedyny typ, który może przybrać wartość nil. Stałe lub zmienne nie mogą być zainicjowane z wartością nil, tej wartości również nie można im przypisać. To jest fragment stosowanej przez język Swift polityki zapewnienia bezpieczeństwa, wszystkie stałe i zmienne inne niż optional muszą mieć wartość.

5. Zarządzanie pamięcią

Jak zapewne pamiętasz, gdy firma Apple wprowadziła mechanizm ARC, do definiowania własności obiektu były używane słowa kluczowe strong i weak. W języku Swift również znajduje się ten model zarządzaniawłasnością, ale wprowadzono do niego nowe słowo kluczowe unowned. Spójrzmy teraz na poszczególne modele własności obiektu w Swifcie.

strong

W języku Swift domyślnie są stosowane odwołania typu strong. W większości przypadków jesteś właścicielem obiektu, do którego się odwołujesz, a tym samym pozostajesz odpowiedzialny za „utrzymanie go przy życiu”.

Ponieważ tego typu odwołania są stosowane domyślnie, nie ma konieczności wyraźnego stosowania odwołania typu strong. Każde nieoznaczone odwołanie jest odwołaniem typu strong.

weak

Odwołanie typu weak w języku Swift wskazuje na brak odpowiedzialności za „utrzymanie przy życiu” wskazanego obiektu. Tego rodzaju odwołanie najczęściej występuje między obiektami, które nie potrzebują się nawzajem, aby nadal mogły istnieć.

Jednak istnieje jedna ważna kwestia. W języku Swift odwołanie typu weak zawsze musi być zmienną typu optional, ponieważ po usunięciu obiektu zmiennej przypisywana jest wartość nil. Do zdefiniowania zmiennej jako weak jest używane słowo kluczowe weak:

unowned

Odwołanie typu unowned jest nowością dla programistów Objective-C. Tego rodzaju odwołanie oznacza brak odpowiedzialności za utrzymanie obiektu przy życiu, czyli podobnie jak w odwołaniu typu weak.

Różnica między odwołaniami typu weak i unowned polegana tym, że drugie z wymienionych nie będzie miało przypisanej wartości nil po usunięciu obiektu wskazywanego przez odwołanie. Kolejna ważna różnica w porównaniu do odwołania typu weak polega na tym, że odwołania typu unowned są definiowane jako innego typu niż optional.

Odwołanie unowned może być stałą. Wskazywany przez to odwołanie obiekt nie istnieje bez swojego właściciela, a tym samym nigdy nie będzie miał przypisanej wartości nil. W przypadku tego typu odwołania konieczne jest użycie słowa kluczowego unowned przed definicją stałej lub zmiennej:

Podsumowanie

Swift to niesamowity język programowania oferujący potężne możliwości i ogromny potencjał. Tworzenie programów w tym języku sprawia wiele radości, a ponadto nie wymaga dodatkowego kodu, który tworzyliśmy w Objective-C, aby zapewnić bezpieczeństwo kodu.

Gorąco zachęcam Cię do zapoznania się z pozycją The Swift Programming Language, która jest bezpłatnie dostępna w iBooks Store.

Advertisement
Advertisement
Advertisement
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.