Bawąc się Swiftem, wywodzącym się ze środowiska Java, dlaczego miałbyś wybrać Strukturę zamiast klasy? Wygląda na to, że są tym samym, a Struct oferuje mniej funkcjonalności. Dlaczego więc to wybrać?
swift
class
struct
design-principles
bluedevil2k
źródło
źródło
Odpowiedzi:
Zgodnie z bardzo popularnym przemówieniem WWDC 2015 Programowanie zorientowane na protokół w Swift ( wideo , transkrypcja ), Swift oferuje szereg funkcji, które w wielu okolicznościach sprawiają, że struktury są lepsze niż klasy.
Struktury są preferowane, jeśli są stosunkowo małe i można je kopiować, ponieważ kopiowanie jest znacznie bezpieczniejsze niż posiadanie wielu odwołań do tego samego wystąpienia, co dzieje się w przypadku klas. Jest to szczególnie ważne przy przekazywaniu zmiennej do wielu klas i / lub w środowisku wielowątkowym. Jeśli zawsze możesz wysłać kopię swojej zmiennej do innych miejsc, nigdy nie musisz się martwić, że to inne miejsce zmieni wartość twojej zmiennej pod tobą.
Dzięki Strukturom nie musisz martwić się o wycieki pamięci lub ściganie się wielu wątków, aby uzyskać dostęp / modyfikować pojedyncze wystąpienie zmiennej. (Dla bardziej technicznie myślących wyjątkiem jest to podczas przechwytywania struktury wewnątrz zamknięcia, ponieważ wtedy faktycznie przechwytuje odwołanie do instancji, chyba że wyraźnie zaznaczysz ją do skopiowania).
Klasy mogą również ulec rozdęciu, ponieważ klasa może dziedziczyć tylko z jednej nadklasy. To zachęca nas do tworzenia ogromnych superklas obejmujących wiele różnych umiejętności, które są jedynie luźno powiązane. Korzystanie z protokołów, zwłaszcza z rozszerzeniami protokołów, w których można zapewnić implementacje protokołów, pozwala wyeliminować potrzebę klas, aby osiągnąć tego rodzaju zachowanie.
W wykładzie przedstawiono te scenariusze, w których preferowane są klasy:
Oznacza to, że struktury powinny być domyślnymi, a klasy powinny być rezerwowe.
Z drugiej strony dokumentacja Swift Programming Language jest nieco sprzeczna:
Tutaj twierdzi, że powinniśmy domyślnie używać klas i używać struktur tylko w określonych okolicznościach. Ostatecznie musisz zrozumieć rzeczywiste implikacje typów wartości w porównaniu z typami referencyjnymi, a następnie możesz podjąć świadomą decyzję, kiedy użyć struktur lub klas. Pamiętaj też, że koncepcje te ciągle ewoluują, a dokumentacja The Swift Programming Language została napisana przed przemówieniem na temat programowania zorientowanego na protokół.
źródło
In practice, this means that most custom data constructs should be classes, not structures.
Czy możesz mi wyjaśnić, jak po przeczytaniu tego dostajesz, że większość zbiorów danych powinna być strukturami, a nie klasami? Dali określony zestaw reguł, kiedy coś powinno być strukturą, i prawie powiedzieli „wszystkie inne scenariusze, w których klasa jest lepsza”.Ponieważ instancje struktur są przydzielane na stosie, a instancje klas są przydzielane na stercie, struktury mogą czasami być znacznie szybsze.
Jednak zawsze powinieneś zmierzyć to sam i zdecydować na podstawie unikalnego przypadku użycia.
Rozważ następujący przykład, który pokazuje 2 strategie zawijania
Int
typu danych za pomocąstruct
iclass
. Używam 10 powtarzających się wartości, aby lepiej odzwierciedlić rzeczywisty świat, w którym masz wiele pól.Wydajność jest mierzona za pomocą
Kod można znaleźć na https://github.com/knguyen2708/StructVsClassPerformance
AKTUALIZACJA (27 marca 2018 r.) :
Począwszy od Swift 4.0, Xcode 9.2, z uruchomioną wersją Release na iPhone 6S, iOS 11.2.6, ustawienia kompilatora Swift to
-O -whole-module-optimization
:class
wersja zajęła 2.06 sekundstruct
wersja zajęła 4.17e-08 sekund (50 000 000 razy szybciej)(Już nie uśredniam wielu przebiegów, ponieważ wariancje są bardzo małe, poniżej 5%)
Uwaga : różnica jest znacznie mniej dramatyczna bez optymalizacji całego modułu. Byłbym zadowolony, gdyby ktoś mógł wskazać, co faktycznie robi flaga.
AKTUALIZACJA (7 maja 2016 r.) :
Począwszy od Swift 2.2.1, Xcode 7.3, z uruchomioną wersją Release na iPhone 6S, iOS 9.3.1, uśredniony dla 5 uruchomień, ustawienie kompilatora Swift to
-O -whole-module-optimization
:class
wersja zajęła 2.159942142sstruct
wersja zajęła 5.83E-08s (37.000.000 razy szybciej)Uwaga : jak ktoś wspomniał, że w rzeczywistych scenariuszach w strukturze będzie prawdopodobnie więcej niż 1 pole, dodałem testy struktur / klas z 10 polami zamiast 1. Zaskakujące, wyniki nie różnią się zbytnio.
ORYGINALNE WYNIKI (1 czerwca 2014 r.):
(Ran na struct / class z 1 polem, a nie 10)
Począwszy od wersji Swift 1.2, Xcode 6.3.2, z uruchomioną wersją Release na iPhone'a 5S, iOS 8.3, uśredniono dla 5 przebiegów
class
wersja zajęła 9,788332333sstruct
wersja zajęła 0,010532942s (900 razy szybciej)STARE WYNIKI (z nieznanego czasu)
(Ran na struct / class z 1 polem, a nie 10)
W wersji kompilacji na moim MacBooku Pro:
class
Wersja trwało 1.10082 sekstruct
Wersja trwało 0.02324 sekund (50 razy szybciej)źródło
Podobieństwa między strukturami i klasami.
Stworzyłem do tego istotę za pomocą prostych przykładów. https://github.com/objc-swift/swift-classes-vs-structures
I różnice
1. Dziedziczenie.
struktury nie mogą dziedziczyć w trybie szybkim. Jeśli chcesz
Idź na zajęcia.
2. Przejdź obok
Struktury szybkie przechodzą według wartości, a instancje klasy są przekazywane przez odwołanie.
Różnice kontekstowe
Stała konstrukcyjna i zmienne
Przykład (używany podczas WWDC 2014)
Definiuje strukturę o nazwie Punkt.
Teraz, jeśli spróbuję zmienić x. To prawidłowe wyrażenie.
Ale gdybym zdefiniował punkt jako stały.
W tym przypadku cały punkt jest niezmienną stałą.
Jeśli zamiast tego użyłem klasy Point, jest to prawidłowe wyrażenie. Ponieważ w niezmiennej stałej klasy jest odwołanie do samej klasy, a nie do jej zmiennych instancji (chyba że zmienne zdefiniowane jako stałe)
źródło
Oto kilka innych powodów do rozważenia:
Struktury otrzymują automatyczny inicjator, którego wcale nie musisz utrzymywać w kodzie.
Aby uzyskać to w klasie, musisz dodać inicjator i utrzymać intializator ...
Podstawowe typy kolekcji, takie jak
Array
struktury. Im częściej używasz ich we własnym kodzie, tym bardziej przyzwyczaisz się do przekazywania wartości zamiast do referencji. Na przykład:Najwyraźniej niezmienność vs. zmienność to ogromny temat, ale wielu inteligentnych ludzi uważa, że niezmienność - w tym przypadku struktury - jest lepsza. Obiekty zmienne a niezmienne
źródło
internal
zakresem.mutating
, abyś wyraźnie wiedział, jakie funkcje zmieniają ich stan. Ale ich natura jako typów wartości jest ważna. Jeśli zadeklarujesz strukturę przy pomocylet
, nie będziesz mógł wywoływać na niej żadnych funkcji mutujących. Wideo WWDC 15 na temat lepszego programowania dzięki typom wartości jest doskonałym źródłem informacji na ten temat.Zakładając, że wiemy, że Struct jest typem wartości, a Class jest typem referencyjnym .
Jeśli nie wiesz, jaki jest typ wartości i typ odwołania, zobacz Jaka jest różnica między przekazywaniem przez referencję a przekazywaniem przez wartość?
Na podstawie postu mikeash :
Osobiście nie nazywam tak swoich zajęć. Zazwyczaj nazywam mój UserManager zamiast UserController, ale pomysł jest taki sam
Ponadto nie używaj klasy, gdy musisz zastąpić każdą instancję funkcji, tzn. Nie mają one żadnej wspólnej funkcji.
Zamiast mieć kilka podklas klasy. Użyj kilku struktur zgodnych z protokołem.
Innym uzasadnionym przypadkiem struktur jest sytuacja, gdy chcesz zrobić różnicę / różnicę starego i nowego modelu. W przypadku typów referencji nie można tego zrobić od razu po wyjęciu z pudełka. W przypadku typów wartości mutacje nie są współużytkowane.
źródło
Niektóre zalety:
źródło
Struktura jest znacznie szybsza niż klasa. Ponadto, jeśli potrzebujesz dziedziczenia, musisz użyć klasy. Najważniejsze jest to, że klasa jest typem odniesienia, natomiast struktura jest typem wartości. na przykład,
teraz pozwala stworzyć instancję obu.
teraz pozwala przekazać te wystąpienia do dwóch funkcji, które modyfikują identyfikator, opis, miejsce docelowe itp.
również,
więc,
teraz, jeśli wydrukujemy identyfikator i opis lotu, otrzymamy
Tutaj możemy zobaczyć identyfikator i opis FlightA został zmieniony, ponieważ parametr przekazany do metody modyfikacji faktycznie wskazuje adres pamięci obiektu flightA (typ odniesienia).
teraz, jeśli wydrukujemy identyfikator i opis instancji FLightB, otrzymamy,
Tutaj widzimy, że instancja FlightB nie ulega zmianie, ponieważ w metodzie editFlight2 rzeczywista instancja Flight2 jest przekazywana, a nie referencyjna (typ wartości).
źródło
Here we can see that the FlightB instance is not changed
Structs
sąvalue type
iClasses
sąreference type
Użyj
value
typu, gdy:Użyj
reference
typu, gdy:Więcej informacji można również znaleźć w dokumentacji Apple
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Dodatkowe informacje
Szybkie typy wartości są przechowywane na stosie. W procesie każdy wątek ma swoją własną przestrzeń stosu, więc żaden inny wątek nie będzie miał bezpośredniego dostępu do typu wartości. Stąd brak warunków wyścigu, blokad, zakleszczeń lub powiązanej złożoności synchronizacji wątków.
Typy wartości nie wymagają dynamicznego przydzielania pamięci ani liczenia referencji, które są kosztownymi operacjami. Jednocześnie metody dla typów wartości są wysyłane statycznie. Stanowią one ogromną przewagę na korzyść rodzajów wartości pod względem wydajności.
Dla przypomnienia jest tu lista Swift
Typy wartości:
Typy referencyjne:
źródło
Odpowiadając na pytanie z perspektywy typów wartości a typów referencyjnych, z tego postu na blogu Apple byłoby to bardzo proste:
Jak wspomniano w tym artykule, klasa bez właściwości do zapisu będzie zachowywać się identycznie ze strukturą, z (dodam) jednym zastrzeżeniem: struktury są najlepsze dla modeli bezpiecznych dla wątków - jest to coraz większe wymaganie we współczesnej architekturze aplikacji.
źródło
W przypadku klas otrzymujesz dziedziczenie i są przekazywane przez referencje, struktury nie mają dziedziczenia i są przekazywane przez wartość.
Na platformie Swift odbywają się świetne sesje WWDC, na to szczegółowe pytanie udzielono szczegółowo w jednej z nich. Oglądaj je, ponieważ przyspieszy to znacznie szybciej niż Przewodnik językowy lub iBook.
źródło
Nie powiedziałbym, że struktury oferują mniejszą funkcjonalność.
Oczywiście, jaźń jest niezmienna, z wyjątkiem funkcji mutacji, ale o to chodzi.
Dziedziczenie działa dobrze, dopóki trzymasz się starego, dobrego pomysłu, że każda klasa powinna być abstrakcyjna lub ostateczna.
Implementuj klasy abstrakcyjne jako protokoły, a klasy końcowe jako struktury.
Zaletą struktur jest to, że możesz modyfikować swoje pola bez tworzenia współdzielonego stanu mutable, ponieważ zajmuje się tym kopiowanie przy zapisie :)
Dlatego wszystkie właściwości / pola w poniższym przykładzie są zmienne, czego nie zrobiłbym w Javie, C # lub klasach szybkich .
Przykładowa struktura dziedziczenia z nieco brudnym i prostym użyciem u dołu w funkcji o nazwie „przykład”:
źródło
Wzór kreacyjny:
W skrócie Struct to typy wartości które są automatycznie klonowane. Dlatego otrzymujemy wymagane zachowanie do zaimplementowania wzorca prototypu za darmo.
Natomiast klasy są typem referencyjnym, który nie jest automatycznie klonowany podczas przypisywania. Aby zaimplementować wzorzec prototypowy, klasy muszą przyjąć
NSCopying
protokół.Płytka kopia powiela tylko odniesienie, które wskazuje na te obiekty, natomiast głęboka kopia powiela odniesienie do obiektu.
Wdrożenie głębokiej kopii dla każdego typu odniesienia stało się żmudnym zadaniem. Jeśli klasy zawierają dodatkowy typ odwołania, musimy zaimplementować wzorzec prototypu dla każdej właściwości odwołania. A następnie musimy skopiować cały wykres obiektów, implementując
NSCopying
protokół.Używając struktur i wyliczeń , uprościliśmy nasz kod, ponieważ nie musimy implementować logiki kopiowania.
źródło
Wiele interfejsów API Cocoa wymaga podklas NSObject, co zmusza cię do korzystania z klasy. Ale poza tym możesz użyć następujących przypadków z bloga Swift firmy Apple, aby zdecydować, czy użyć typu wartości struct / enum, czy typu odwołania do klasy.
https://developer.apple.com/swift/blog/?id=10
źródło
Jednym z punktów, na który nie zwraca się uwagi w tych odpowiedziach, jest to, że zmienna utrzymująca klasę względem struktury może przez jakiś
let
czas nadal pozwalać na zmiany właściwości obiektu, podczas gdy nie można tego zrobić za pomocą struktury.Jest to przydatne, jeśli nie chcesz, aby zmienna kiedykolwiek wskazywała na inny obiekt, ale nadal musisz zmodyfikować obiekt, tj. W przypadku wielu zmiennych instancji, które chcesz aktualizować jedna po drugiej. Jeśli jest to struktura, musisz w tym celu zresetować zmienną do innego obiektu
var
, ponieważ stały typ wartości w Swift poprawnie pozwala na zerową mutację, podczas gdy typy referencyjne (klasy) nie zachowują się w ten sposób.źródło
Struktury są typami wartości i można bardzo łatwo utworzyć pamięć, która przechowuje w stosie. Strukt może być łatwo dostępny, a po zakresie pracy można go łatwo zwolnić z pamięci stosu poprzez pop z góry stosu. Z drugiej strony klasa jest typem odniesienia, który przechowuje w stosie, a zmiany dokonane w obiekcie jednej klasy będą miały wpływ na inny obiekt, ponieważ są ściśle powiązane i typu odniesienia. Wszyscy członkowie struktury są publiczni, podczas gdy wszyscy członkowie klasy są prywatni .
Wadą struktury jest to, że nie można jej odziedziczyć.
źródło
Struktura i klasa są typami danych definiowanymi przez użytkownika
Domyślnie struktura jest publiczna, podczas gdy klasa jest prywatna
Klasa implementuje zasadę enkapsulacji
Obiekty klasy są tworzone w pamięci sterty
Klasa służy do ponownego wykorzystania, podczas gdy struktura służy do grupowania danych w tej samej strukturze
Elementy danych struktury nie mogą być inicjowane bezpośrednio, ale można je przypisać na zewnątrz struktury
Elementy danych klasy mogą być inicjowane bezpośrednio przez konstruktor bez parametrów i przypisywane przez sparametryzowany konstruktor
źródło