iOS Small Talks: Klasy i struktury w języku Swift

iOS Small Talks: Klasy i struktury w języku Swift
Język Swift wprowadził znaczne udoskonalenie struktur, w związku z czym stały się one ciekawą alternatywą dla klas w wielu przypadkach. Spora część możliwości klasy i struktury jest analogiczna, jednak istnieje kilka różnic, dzięki którym warto rozważyć przydatność każdej z form w konkretnej sytuacji.

Wspólne cechy klas i struktur

  • właściwości przechowujące konkretne wartości,
  • definiowanie metod w celu zapewnienia funkcjonalności,
  • definiowanie subskryptów, za pomocą których można uzyskać dostęp do wybranej wartości,
  • inicjalizatory, dzięki którym nadajemy stan początkowy,
  • rozszerzanie funkcjonalności poza podstawową implementację,
  • dostosowywanie się do protokołów.

Najważniejsze różnice

  • możliwość dziedziczenia dla klas oraz jej brak dla struktury,
  • dla klasy można zdefiniować deinicjalizatory,
  • klasy są typem przekazywanym przez referencję, natomiast struktury przekazywane są przez wartość.

Tworzenie instancji klasy i struktury jest bardzo podobne. Różni się jedynie słowem kluczowym class lub struct. Dostęp do właściwości również jest analogiczny. Różnicę dostrzeżemy natomiast w momencie tworzenia instancji. Dla struktury na podstawie wszystkich właściwości zostanie automatycznie utworzony inteligentny inicjalizator.

Załóżmy, że nasza struktura wygląda tak, jak ta poniżej:

struct Coordinates {
            var x : Float
            var y : Float
            var z : Float
}

Zostanie dla niej utworzony następujący inicjalizator, gdzie wartości x,y,z należy oczywiście uzupełnić:

var myCoordinates = Coordinates(x: , y: , z: )

Z racji faktu, iż klasa jest typem referencyjnym, natomiast struktura wartościowym, wynikać będzie różnica w zachowaniu w momencie przypisania do nowej instancji już utworzonego obiektu i wykonaniu operacji na tej instancji. Przykładowo dla wyżej wymienionej struktury:

var myCoordinates = Coordinates(x: 0 , y: 0, z: 0)
var anotherCoordinates = myCoordinates
anotherCoordinates.x = 22

Po wykonaniu takich operacji właściwość x dla myCoordinates nadal wynosi 0, natomiast dla anotherCoordinates 22. Dzieje się tak za sprawą tego, iż w momencie przypisania myCoordinates do anotherCoordinates nie została przekazana referencja do obiektu, a jego kopia. W momencie wykonania analogicznych operacji na instancjach klasy wartość zmiennej x dla obu z nich wynosiłaby aktualnie 22.

W przypadku użycia instancji struktury jako parametru metody, również wysyłana jest kopia, a w związku z tym zmiany wykonane w trakcie działania metody zostaną zapamiętane jedynie lokalnie w jej zakresie. Aby zmiany zostały uwzględnione w naszej instancji globalnie, należy oznaczyć parametr metody jako inout. Dzięki temu znacznikowi, parametrom przekazywanym do funkcji przez wartość przypisane zostają zmiany wykonane w trakcie funkcji.

func changeCoordinates(inout coordinates: Coordinates) {
	coordinates.x = 88
	coordinates.y = 100
}

changeCoordinates(&myCoordinates)

Po wykonaniu takich działań wartości x oraz y instancji myCoordinates będą wynosić 88 oraz 100. Warto zauważyć, że w tym przypadku, w przeciwieństwie do standardowego podawania parametrów funkcji, parametr myCoordinates poprzedzony jest znakiem &.

Kolejną zauważalną różnicą są następstwa wynikające ze zdefiniowania instancji klasy bądź też struktury jako stałej. Dla klasy po utworzeniu stałej obiektowej nie można zmienić obiektu do którego się odnosi, natomiast możliwe jest edytowanie właściwości obiektu przypisanego do tej stałej. Dla struktury zaś właściwości obiektu również stają się niezmienne. Obrazuje to poniższy przykład:

  • dla klasy:
let myCoordinates = Coordinates(x: 0 , y: 0, z: 0)
var anotherCoordinates = myCoordinates

myCoordinates = anotherCoordinates  // błąd

myCoordinates.x = 22 // dozwolona operacja
  • dla struktury:
let myCoordinates = Coordinates(x: 0 , y: 0, z: 0)
var anotherCoordinates = myCoordinates
myCoordinates = anotherCoordinates  // błąd
myCoordinates.x = 22 // również błąd

Struktury wprowadzają również zabezpieczenie dotyczące metod, które zmieniają właściwości owej struktury. Na podstawie powyższej struktury dodanie do niej metody:

func increaseByTwo() {
            self.x = self.x + 2
}

jest niedozwolone, gdyż zmienia ona parametr self. Aby umożliwić metodzie takie operacje, należy dodać do niej słowo kluczowe mutating.

mutating func increaseByTwo() {
            self.x = self.x + 2
}

Teraz kompilator już nie potraktuje tej metody jako błąd.

Struktury czy klasy?

Na podstawie różnych możliwości, oferowanych przez struktury oraz klasy, nasuwa się pytanie, kiedy należy stosować struktury, a kiedy też klasy. Dokumentacja firmy Apple wspomina o tym, iż podstawowym celem struktury jest przechowywanie względnie prostych wartości. Wartości te w momencie przesyłania instancji struktury powinny być wartościami, które chcemy kopiować, a nie podawać ich referencje. Przy wyborze warto pamiętać również, o tym że struktury nie umożliwiają dziedziczenia.

Jako dobry przykład struktury zostały wymienione:

  • rozmiary kształtów geometrycznych,
  • różnego rodzaju współrzędne.

W przypadku napotkania kandydata odpowiedniego na strukturę, warto jej użyć, ponieważ jej wydajność jest lepsza niż wydajność klasy dla obiektów zawierających jedynie kilka prostych właściwości.

Na koniec warto wspomnieć, iż w języku Swift tablice oraz słowniki również są strukturami. W związku z tym faktem w momencie przypisania jednej instancji tablicy innej tablicy dane są kopiowane, co skutkuje tym, że zmiana w jednej tablicy nie jest odzwierciedlona w drugiej. W Objective-C aby uzyskać takie zachowanie należało celowo utworzyć kopię tablicy za pomocą metody copy.

Dowiedz się więcej

Dynamiczna lokalizacja powiadomień na iOS-a, czyli jak wysyłać wiadomości w różnych językach

Jak ustanowić łącznik pomiędzy użytkownikiem, dostawcą a aplikacją? Wykorzystaj powiadomienia systemowe. Dzięki nim użytkownik szybko otrzymuje najnowsze informacje. To rozwiązanie łatwo zastosujesz w aplikacjach zorientowanych na jeden rynek. Problemy zaczynają się, gdy treści muszą być tworzone w różnych językach. Wtedy pomocą służy dynamiczna lokalizacja zdalnych powiadomień bez wykorzystania własnego serwera.
Przeczytaj

Jak wykorzystać feature flags, żeby zyskać większą kontrolę nad aplikacją?

Chyba każdy, kto zajmuje się budową oprogramowania może opowiedzieć kilka historii o niedziałających funkcjach. Starannie tworzymy aplikacje z niewielkich elementów, stosujemy zaawansowane wzorce architektury, ale i tak czasem coś odmawia posłuszeństwa. Skutkuje to błędami, a nawet awarią systemu. Wtedy sytuację może uratować feature toggling. Sprawdź, jak wdrożyć feature flags i zwiększ stabilność swojej aplikacji.
Przeczytaj

WebSockets na iOS-a – komunikacja w czasie rzeczywistym, która nie spowalnia aplikacji

Kiedy chcesz pobrać dane do aplikacji, zwykle pewnie wykorzystujesz interfejs API RESTful. Wystarczy zapytać serwer o paczkę danych i gotowe. To dobra metoda, jeśli aplikacja nie potrzebuje stałego dostępu do nowych informacji. A co jeśli treści muszą się odświeżać w czasie rzeczywistym? Wtedy opóźnienie może np. uniemożliwiać dokonanie zakupu albo postawienie zakładu sportowego. Na szczęście z pomocą przychodzą WebSockets na iOS-a. Sprawdź, jak je zaimplementować i dlaczego warto to zrobić.
Przeczytaj

Wycena projektu

Sprawdź, jak wykorzystujemy naszą wiedzę w praktyce i stwórz z nami swój projekt.

Dlaczego warto rozwijać z nami projekty?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nominacja w kategorii
M-COMMERCE

17

opinii klientów

Clutch logo
Logo Legalni bukmacherzy

Nagroda Legalnych Bukmacherów 2019

Najlepsza aplikacja mobilna

60+

zrealizowanych projektów