Dlaczego warto wybrać Struct Over Class?

476

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ć?

bluedevil2k
źródło
11
Struktury są zawsze kopiowane, gdy są przekazywane w kodzie i nie używają liczenia referencji. źródło: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex
4
Powiedziałbym, że struktury są bardziej odpowiednie do przechowywania danych, a nie logiki. Mówiąc językiem Java, wyobraź sobie struktury jako „Obiekty wartości”.
Vincent Guerci
6
Dziwi mnie cała ta rozmowa, w której nie ma bezpośredniej wzmianki o kopiowaniu przy zapisie, czyli leniwej kopii . Wszelkie obawy dotyczące wydajności kopiowania struktur są w większości dyskusyjne z powodu tego projektu.
David James
3
Wybór struktury nad klasą nie jest kwestią opinii. Istnieją konkretne powody, aby wybrać jedno lub drugie.
David James
Bardzo polecam zobaczyć, dlaczego tablica nie jest ThreadSafe . Jest to powiązane, ponieważ tablice i struktury są typami wartości. Wszystkie odpowiedzi tutaj wspominają, że w typach struktur / tablic / wartości nigdy nie będzie występował problem dotyczący bezpieczeństwa wątku, ale istnieje przypadek narożny, w którym będziesz.
Honey,

Odpowiedzi:

548

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:

  • Kopiowanie lub porównywanie instancji nie ma sensu (np. Window)
  • Czas życia instancji jest powiązany z efektami zewnętrznymi (np. Plik tymczasowy)
  • Instancje to tylko „ujścia” - przewody tylko do zapisu do stanu zewnętrznego (np.CGContext)

Oznacza to, że struktury powinny być domyślnymi, a klasy powinny być rezerwowe.

Z drugiej strony dokumentacja Swift Programming Language jest nieco sprzeczna:

Instancje struktury są zawsze przekazywane przez wartość, a instancje klasy są zawsze przekazywane przez referencję. Oznacza to, że nadają się do różnego rodzaju zadań. Rozważając konstrukcje danych i funkcje potrzebne do projektu, zdecyduj, czy każda konstrukcja danych powinna być zdefiniowana jako klasa czy struktura.

Jako ogólną wskazówkę rozważ utworzenie struktury, gdy spełniony zostanie jeden lub więcej z tych warunków:

  • Podstawowym celem struktury jest zamknięcie kilku stosunkowo prostych wartości danych.
  • Rozsądne jest oczekiwanie, że enkapsulowane wartości zostaną skopiowane, a nie odwołane, gdy przypiszesz lub przekażesz instancję tej struktury.
  • Wszelkie właściwości przechowywane przez strukturę same w sobie są typami wartości, które również powinny zostać skopiowane, a nie odwołane.
  • Struktura nie musi dziedziczyć właściwości ani zachowania z innego istniejącego typu.

Przykłady dobrych kandydatów do struktur obejmują:

  • Rozmiar kształtu geometrycznego, być może zawierającego właściwość width i height, oba typu Double.
  • Sposób odwoływania się do zakresów w szeregu, być może zawierających właściwość początkową i właściwość długości, oba typu Int.
  • Punkt w układzie współrzędnych 3D, być może zawierający właściwości x, y i z, każdy typu Double.

We wszystkich innych przypadkach zdefiniuj klasę i utwórz instancje tej klasy, które będą zarządzane i przekazywane przez referencję. W praktyce oznacza to, że większość niestandardowych konstrukcji danych powinna być klasami, a nie strukturami.

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ół.

Drawag
źródło
11
@ElgsQianChen cały ten zapis polega na tym, że struct powinien być wybierany domyślnie, a klasa powinna być używana tylko wtedy, gdy jest to konieczne. Struktury są znacznie bezpieczniejsze i wolne od błędów, szczególnie w środowisku wielowątkowym. Tak, zawsze możesz użyć klasy zamiast struktury, ale struktury są preferowane.
drewag
16
@drewag To wydaje się być dokładnym przeciwieństwem tego, co mówi. Mówiło się, że klasa powinna być domyślną, której używasz, a nie strukturą. 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”.
Matt
42
Ostatnia linijka powinna brzmieć: „Moja osobista rada jest przeciwieństwem dokumentacji:”… a następnie jest to świetna odpowiedź!
Dan Rosenstark,
5
Książka Swift 2.2 nadal mówi, że w większości sytuacji używaj klas.
David James
6
Struktura ponad klasą zdecydowanie zmniejsza złożoność. Ale jaki jest wpływ na użycie pamięci, gdy struktury stają się domyślnym wyborem. Kiedy rzeczy są kopiowane wszędzie zamiast odniesienia, powinno to zwiększyć wykorzystanie pamięci przez aplikację. Nie powinno?
MadNik,
164

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 Inttypu danych za pomocą structi class. Używam 10 powtarzających się wartości, aby lepiej odzwierciedlić rzeczywisty świat, w którym masz wiele pól.

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

Wydajność jest mierzona za pomocą

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

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 sekund
  • struct 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.159942142s
  • struct 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,788332333s
  • struct 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:

  • The classWersja trwało 1.10082 sek
  • structWersja trwało 0.02324 sekund (50 razy szybciej)
Khanh Nguyen
źródło
27
To prawda, ale wydaje się, że kopiowanie kilku struktur byłoby wolniejsze niż kopiowanie odwołania do pojedynczego obiektu. Innymi słowy, szybsze jest skopiowanie pojedynczego wskaźnika niż skopiowanie dowolnego dużego bloku pamięci.
Tylerc230,
14
-1 Ten test nie jest dobrym przykładem, ponieważ w strukturze jest tylko jedna zmienna. Zauważ, że jeśli dodasz kilka wartości i obiekt lub dwie, wersja struct stanie się porównywalna z wersją klasy. Im więcej zmiennych dodasz, tym wolniejsza staje się wersja struct.
joshrl
6
@joshrl ma rację, ale przykład jest „dobry” lub nie, zależy od konkretnej sytuacji. Ten kod został wyodrębniony z mojej aplikacji, więc jest to prawidłowy przypadek użycia, a użycie struktur znacznie poprawiło wydajność mojej aplikacji. Prawdopodobnie nie jest to częsty przypadek użycia (no cóż, w przypadku większości aplikacji nikt nie dba o szybkość przesyłania danych, ponieważ wąskie gardło zdarza się gdzie indziej, np. Połączenia sieciowe, w każdym razie optymalizacja to nie to, że krytyczne, gdy masz urządzenia GHz z GB lub RAM).
Khanh Nguyen
26
O ile rozumiem, kopiowanie w trybie szybkim jest zoptymalizowane pod kątem czasu WRITE. Co oznacza, że ​​nie zostanie wykonana kopia pamięci fizycznej, chyba że nowa kopia ma zostać zmieniona.
Matjan
6
Ta odpowiedź pokazuje niezwykle trywialny przykład, do tego stopnia, że ​​jest nierealna, a zatem błędna w wielu przypadkach. Lepszą odpowiedzią byłoby „to zależy”.
iwasrobbed
60

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

class Vehicle{
}

class Car : Vehicle{
}

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)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Definiuje strukturę o nazwie Punkt.

var point = Point(x:0.0,y:2.0)

Teraz, jeśli spróbuję zmienić x. To prawidłowe wyrażenie.

point.x = 5

Ale gdybym zdefiniował punkt jako stały.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

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)

MadNik
źródło
Struktury
thatguy,
12
Powyższa treść dotyczy tego, w jaki sposób możemy osiągnąć smaki dziedziczenia dla struct. Zobaczysz składnię podobną do. Odp .: B. Jest to struct o nazwie A implementuje protokół o nazwie B. Dokumentacja Apple wyraźnie wspomina, że ​​struct nie obsługuje czystego dziedziczenia i nie obsługuje.
MadNik,
2
człowieku, ten ostatni akapit był świetny. Zawsze wiedziałem, że możesz zmienić stałe ... ale od czasu do czasu widziałem, gdzie nie możesz, więc byłem zaskoczony. To rozróżnienie sprawiło, że było to widoczne
Honey
28

Oto kilka innych powodów do rozważenia:

  1. Struktury otrzymują automatyczny inicjator, którego wcale nie musisz utrzymywać w kodzie.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")

Aby uzyskać to w klasie, musisz dodać inicjator i utrzymać intializator ...

  1. Podstawowe typy kolekcji, takie jak Arraystruktury. Im częściej używasz ich we własnym kodzie, tym bardziej przyzwyczaisz się do przekazywania wartości zamiast do referencji. Na przykład:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. 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

Dan Rosenstark
źródło
4
To prawda, że ​​otrzymujesz automatyczne inicjalizatory. Otrzymujesz również pusty inicjator, gdy wszystkie właściwości są opcjonalne. Ale jeśli masz strukturę w frameworku, musisz sam napisać inicjalizator, jeśli chcesz, aby był dostępny poza internalzakresem.
Abizern,
2
@Abizern potwierdził - stackoverflow.com/a/26224873/8047 - i człowiek, który jest denerwujący.
Dan Rosenstark,
2
@Abizern istnieją wspaniałe powody, dla których wszystko jest w Swift, ale za każdym razem, gdy coś jest prawdą w jednym miejscu, a nie w innym, programista musi wiedzieć więcej. Myślę, że oto, co powinienem powiedzieć: „praca w tak wymagającym języku jest ekscytująca!”
Dan Rosenstark,
4
Czy mogę również dodać, że to nie niezmienność struktur czyni je użytecznymi (chociaż jest to bardzo dobra rzecz). Możesz mutować struktury, ale musisz oznaczyć metody jako 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 pomocy let, 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.
Abizern,
1
Dzięki @Abizern, nigdy tak naprawdę tego nie zrozumiałem przed przeczytaniem twojego komentarza. W przypadku obiektów let vs. var nie ma dużej różnicy, ale dla struktur jest ogromny. Dzięki za zwrócenie na to uwagi.
Dan Rosenstark,
27

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 :

... Najpierw przyjrzyjmy się ekstremalnym, oczywistym przykładom. Liczby całkowite są oczywiście możliwe do skopiowania. Powinny być typami wartości. Nie można rozsądnie skopiować gniazd sieciowych. Powinny to być typy referencyjne. Punkty, jak w parach x, y, można kopiować. Powinny być typami wartości. Kontrolera reprezentującego dysk nie można rozsądnie skopiować. To powinien być typ odniesienia.

Niektóre typy można kopiować, ale nie zawsze może to być coś, co chcesz zrobić. Sugeruje to, że powinny to być typy referencyjne. Na przykład przycisk na ekranie można koncepcyjnie skopiować. Kopia nie będzie identyczna z oryginałem. Kliknięcie kopii nie aktywuje oryginału. Kopia nie zajmie tej samej lokalizacji na ekranie. Jeśli przekażesz przycisk lub umieścisz go w nowej zmiennej, prawdopodobnie będziesz chciał odwołać się do oryginalnego przycisku i będziesz chciał wykonać kopię tylko wtedy, gdy zostanie to wyraźnie wymagane. Oznacza to, że typ przycisku powinien być typem referencyjnym.

Kontrolery widoku i okna są podobnym przykładem. Możliwe, że można je skopiować, ale prawie nigdy nie chcesz tego robić. Powinny to być typy referencyjne.

Co z typami modeli? Możesz mieć typ użytkownika reprezentujący użytkownika w twoim systemie lub typ przestępstwa reprezentujący działanie podjęte przez użytkownika. Można je kopiować, więc prawdopodobnie powinny to być typy wartości. Prawdopodobnie jednak chcesz, aby aktualizacje przestępstwa użytkownika dokonane w jednym miejscu w programie były widoczne dla innych części programu. Sugeruje to, że Twoimi użytkownikami powinien zarządzać jakiś kontroler użytkowników, który byłby typem odniesienia . na przykład

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

Kolekcje są ciekawym przypadkiem. Należą do nich między innymi tablice i słowniki oraz ciągi znaków. Czy można je kopiować? Oczywiście. Czy kopiowanie czegoś, co chcesz zrobić, łatwo i często? To mniej jasne.

Większość języków mówi na to „nie” i podaje swoje typy referencyjne kolekcji. Dzieje się tak w Objective-C i Java, Python i JavaScript oraz prawie w każdym innym języku, jaki mogę wymyślić. (Jednym z głównych wyjątków jest C ++ z typami kolekcji STL, ale C ++ jest szalonym szaleńcem świata języków, który robi wszystko dziwnie.)

Swift powiedział „tak”, co oznacza, że ​​typy takie jak Array i Dictionary i String są strukturami, a nie klasami. Są one kopiowane po przypisaniu i przekazaniu ich jako parametrów. Jest to całkowicie rozsądny wybór, o ile kopia jest tania, co Swift bardzo stara się osiągnąć. ...

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.

miód
źródło
1
Dokładnie takie wyjaśnienie, którego szukałem. Fajnie napisz :)
androCoder-BD
Bardzo pomocny przykład kontrolera
zapytaj P
1
@AskP Sam wysłałem e-mail do Mike'a i dostałem ten dodatkowy kod :)
Honey
18

Niektóre zalety:

  • automatycznie wątkowo bezpieczny, ponieważ nie można go udostępniać
  • zużywa mniej pamięci z powodu braku isa i refcount (i tak naprawdę ogólnie przydzielany jest stos)
  • metody są zawsze wysyłane statycznie, więc można je wstawiać (chociaż @final może to zrobić dla klas)
  • łatwiejsze do uzasadnienia (nie ma potrzeby „defensywnego kopiowania”, co jest typowe dla NSArray, NSString itp.) z tego samego powodu, co bezpieczeństwo wątków
Catfish_Man
źródło
Nie jestem pewien, czy jest to poza zakresem tej odpowiedzi, ale czy możesz wyjaśnić (lub chyba link), że punkt „metody są zawsze wysyłane statycznie”?
Dan Rosenstark,
2
Pewnie. Mogę również załączyć do niego zastrzeżenie. Celem dynamicznej wysyłki jest wybranie implementacji, gdy nie wiesz z góry, której użyć. W Swift może to wynikać z dziedziczenia (może zostać zastąpione w podklasie) lub z powodu funkcji ogólnej (nie wiadomo, jaki będzie parametr ogólny). Struktury nie mogą być dziedziczone, a optymalizacja całego modułu + specjalizacja ogólna w większości eliminuje nieznane rodzaje, więc metody można po prostu wywoływać bezpośrednio zamiast szukać, co należy wywołać. Niespecjalizowane generyczne nadal wykonują dynamiczną wysyłkę dla struktur
Catfish_Man
1
Dzięki, świetne wyjaśnienie. Więc oczekujemy większej szybkości działania, mniejszej niejednoznaczności z perspektywy IDE, czy obu?
Dan Rosenstark,
1
Głównie te pierwsze.
Catfish_Man
Tylko uwaga, że ​​metody nie są wysyłane statycznie, jeśli odwołujesz się do struktury za pomocą protokołu.
Cristik
12

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,

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    } 
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String  
}

teraz pozwala stworzyć instancję obu.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

teraz pozwala przekazać te wystąpienia do dwóch funkcji, które modyfikują identyfikator, opis, miejsce docelowe itp.

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

również,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines" 
}

więc,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

teraz, jeśli wydrukujemy identyfikator i opis lotu, otrzymamy

id = 200
description = "second flight of Virgin Airlines"

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,

id = 100
description = "first ever flight of Virgin Airlines"

Tutaj widzimy, że instancja FlightB nie ulega zmianie, ponieważ w metodzie editFlight2 rzeczywista instancja Flight2 jest przekazywana, a nie referencyjna (typ wartości).

Manoj Karki
źródło
2
nigdy nie stworzyłeś instancji FLightB
David Seek
1
to dlaczego mówisz o bracie FlightB? Here we can see that the FlightB instance is not changed
David Seek
@ManojKarki, świetna odpowiedź. Chciałem tylko zaznaczyć, że dwukrotnie zadeklarowałeś lot A, kiedy myślę, że chcesz zadeklarować lot A, a następnie lot B.
ScottyBlades
11

Structsvalue typei Classesreference type

  • Typy wartości są szybsze niż typy referencyjne
  • Instancje typów wartości są bezpieczne w środowisku wielowątkowym, ponieważ wiele wątków może mutować instancję bez martwienia się o warunki wyścigu lub impasy
  • Typ wartości nie ma odwołań w przeciwieństwie do typu odniesienia; dlatego nie ma wycieków pamięci.

Użyj valuetypu, gdy:

  • Chcesz, aby kopie miały niezależny stan, dane będą używane w kodzie w wielu wątkach

Użyj referencetypu, gdy:

  • Chcesz stworzyć współdzielony, zmienny stan.

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:

  • Struct
  • Enum
  • Tuple
  • Prymitywy (Int, Double, Bool itp.)
  • Kolekcje (tablica, ciąg, słownik, zestaw)

Typy referencyjne:

  • Klasa
  • Wszystko, co pochodzi od NSObject
  • Funkcjonować
  • Zamknięcie
Casillas
źródło
5

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:

Użyj typu wartości [np. Struct, enum], gdy:

  • Porównanie danych instancji z == ma sens
  • Chcesz, aby kopie miały niezależny stan
  • Dane zostaną wykorzystane w kodzie w wielu wątkach

Użyj typu odniesienia [np. Klasa], gdy:

  • Porównanie tożsamości instancji z === ma sens
  • Chcesz stworzyć współdzielony, zmienny stan

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.

David James
źródło
3

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.

Joride
źródło
Czy możesz podać jakieś linki z tego, co wspomniałeś? Ponieważ na WWDC jest całkiem sporo do wyboru, chciałbym obejrzeć ten, który mówi o tym konkretnym temacie
MMachinegun
Dla mnie to dobry początek tutaj: github.com/raywenderlich/…
MMachinegun
2
Prawdopodobnie mówi o tej wspaniałej sesji: Programowanie zorientowane na protokół w Swift. (Linki: wideo , transkrypcja )
zekel
2

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”:

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}
szlachetka
źródło
2

W Swift wprowadzono nowy wzorzec programowania znany jako Programowanie zorientowane na protokół.

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ąć NSCopyingprotokół.


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 NSCopyingprotokół.

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

Używając struktur i wyliczeń , uprościliśmy nasz kod, ponieważ nie musimy implementować logiki kopiowania.

Balasubramanian
źródło
1

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

akshay
źródło
0

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ś letczas 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.

johnbakers
źródło
0

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

Tapash Mollick
źródło
-7
  • 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

Ridaa Zahra
źródło
2
Najgorsza odpowiedź w historii!
J. Doe,
skopiuj odpowiedź wklej
jawadAli