563 Stimmen

Warum Struct anstelle von Klasse wählen?

Beim Spielen mit Swift, kommend aus einem Java-Hintergrund, warum sollte man sich für eine Struktur statt einer Klasse entscheiden? Es scheint, als ob sie dasselbe wären, wobei eine Struktur weniger Funktionalität bietet. Warum also wählen?

22voto

Catfish_Man Punkte 40261

Einige Vorteile:

  • automatisch threadsicher aufgrund der Nichtteilbarkeit
  • benötigt weniger Speicher aufgrund des Fehlens von isa und refcount (wird in der Regel tatsächlich auf dem Stapel allokiert)
  • Methoden werden immer statisch verknüpft, können also inlined werden (obwohl @final dies auch für Klassen tun kann)
  • einfacher zu verstehen (kein Bedarf an „defensivem Kopieren“, wie es typisch bei NSArray, NSString, etc ... ist) aus dem gleichen Grund wie die Threadsicherheit

18voto

casillas Punkte 15671

Structs sind Werttypen und Klassen sind Verweistypen

  • Werttypen sind schneller als Verweistypen
  • Instanzen von Werttypen sind sicher in einer mehrfädigen Umgebung, da mehrere Threads die Instanz mutieren können, ohne sich um Rennbedingungen oder Deadlocks kümmern zu müssen
  • Werttypen haben im Gegensatz zu Verweistypen keine Verweise; daher gibt es keine Speicherlecks.

Verwenden Sie einen Wert Typ, wenn:

  • Sie möchten, dass Kopien einen unabhängigen Zustand haben, die Daten in Code über mehrere Threads verwendet werden

Verwenden Sie einen Verweis Typ, wenn:

  • Sie gemeinsamen, veränderbaren Zustand erstellen möchten.

Weitere Informationen finden Sie auch in der Apple-Dokumentation

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html


Zusätzliche Informationen

Swift-Wertetypen werden im Stapel gehalten. In einem Prozess hat jeder Thread seinen eigenen Stapelplatz, sodass kein anderer Thread direkt auf Ihren Wertetyp zugreifen kann. Daher keine Rennbedingungen, Sperren, Deadlocks oder andere damit verbundene Thread-Synchronisierungskomplexitäten.

Wertetypen benötigen keine dynamische Speicherzuweisung oder Referenzzählung, beides teure Operationen. Gleichzeitig werden Methoden auf Wertetypen statisch ausgeführt. Diese bieten einen erheblichen Vorteil für Wertetypen in Bezug auf die Leistung.

Als Erinnerung hier ist eine Liste von Swift

Wertetypen:

  • Struct
  • Enum
  • Tupel
  • Primitiven (Int, Double, Bool usw.)
  • Sammlungen (Array, String, Dictionary, Set)

Verweistypen:

  • Klasse
  • Alles, was von NSObject kommt
  • Funktion
  • Abbildung

14voto

Manoj Karki Punkte 270

Struktur ist viel schneller als Klasse. Außerdem, wenn Sie Vererbung benötigen, müssen Sie Klasse verwenden. Der wichtigste Punkt ist, dass Klasse ein Referenztyp ist, während Struktur ein Werttyp ist. zum Beispiel,

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "erster Flug von Virgin Airlines"
        destination = "London"
        airlines = "Virgin Airlines"
    } 
}

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

Jetzt erstellen wir Instanzen von beiden.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"erster Flug von Virgin Airlines", destination:"London" , airlines:"Virgin Airlines" )

Jetzt übergeben wir diese Instanzen an zwei Funktionen, die die id, Beschreibung, Ziel usw. ändern.

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "zweiter Flug von Virgin Airlines"
    flight.destination = "New York"
    flight.airlines = "Virgin Airlines"
}

auch,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "zweiter Flug von Virgin Airlines" 
}

Also,

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

Jetzt, wenn wir die id und Beschreibung von FlightA drucken, erhalten wir

id = 200
Beschreibung = "zweiter Flug von Virgin Airlines"

Hier können wir sehen, dass sich die id und Beschreibung von FlightA geändert haben, weil der an die Änderungsmethode übergebene Parameter tatsächlich auf die Speicheradresse des FlugA-Objekts (Referenztyp) zeigt.

Jetzt, wenn wir die id und Beschreibung der Instanz FLightB drucken, erhalten wir,

id = 100
Beschreibung = "erster Flug von Virgin Airlines"

Hier können wir sehen, dass sich die Instanz FlightB nicht geändert hat, weil in der modifyFlight2-Methode tatsächlich die Instanz von Flight2 übergeben wird, anstatt der Referenz (Werttyp).

7voto

David James Punkte 2242

Beim Beantworten der Frage aus der Perspektive von Werttypen vs. Referenztypen erscheint es gemäß diesem Apple-Blog-Beitrag sehr einfach:

Verwenden Sie einen Werttyp [z.B. struct, enum], wenn:

  • das Vergleichen von Instanzdaten mit == sinnvoll ist
  • Sie möchten, dass Kopien einen unabhängigen Zustand haben
  • die Daten in Code über mehrere Threads hinweg verwendet werden

Verwenden Sie einen Referenztyp [z.B. class], wenn:

  • das Vergleichen der Instanzzugehörigkeit mit === sinnvoll ist
  • Sie gemeinsamen, veränderbaren Zustand erstellen möchten

Wie in dem Artikel erwähnt, wird sich eine Klasse ohne schreibbare Eigenschaften identisch wie ein struct verhalten, mit (ich werde hinzufügen) einem Vorbehalt: Structs sind am besten für thread-sichere Modelle geeignet - eine zunehmend dringliche Anforderung in der modernen App-Architektur.

5voto

yoAlex5 Punkte 20661

Struct vs Class

[Stack vs Heap]
[Value vs Reference type]

Struct ist eher bevorzugt. Aber Struct löst nicht alle Probleme von selbst. Normalerweise hört man, dass der Werttyp auf dem Stapel allokiert wird, aber das stimmt nicht immer. Nur lokale Variablen werden auf dem Stapel allokiert

//simple blocks
struct WertTyp {}
class ReferenzTyp {}

struct StructMitRef {
    let ref1 = ReferenzTyp()
}

class KlasseMitRef {
    let ref1 = ReferenzTyp()
}

func foo() {

    //simple  blocks
    let wertTyp1 = WertTyp()
    let refTyp1 = ReferenzTyp()

    //RetainCount
    //StructMitRef
    let structMitRef1 = StructMitRef()
    let structMitRef1Kopie = structMitRef1

    print("Original:", CFGetRetainCount(structMitRef1 as CFTypeRef)) //1
    print("Ref1:", CFGetRetainCount(structMitRef1.ref1)) //2 (ursprünglich 3)

    //KlasseMitRef
    let klasseMitRef1 = KlasseMitRef()
    let klasseMitRef1Kopie = klasseMitRef1

    print("Original:", CFGetRetainCount(klasseMitRef1)) //2 (ursprünglich 3)
    print("Ref1:", CFGetRetainCount(klasseMitRef1.ref1)) //1 (ursprünglich 2)

}

Sie sollten nicht auf retainCount verwenden/verlassen, da es keine nützlichen Informationen liefert

Um Stapel oder Heap zu überprüfen

Während der Kompilierung kann Swift Intermediate Language(SIL) Ihren Code optimieren

swiftc -emit-silgen - .swift
//z.B.
swiftc -emit-silgen -Onone file.swift

//emit-silgen -> emit-sil(ist in jedem Fall verwendet)
//-emit-silgen           Rohes SIL-Datei(en) ausgeben
//-emit-sil              Kanonische SIL-Datei(en) ausgeben
//Optimierung: O, Osize, Onone. Entspricht dem Swift-Compiler - Codegenerierung -> Optimierungsgrad

Dort finden Sie alloc_stack (Speicherung auf Stapel) und alloc_box (Speicherung auf Heap)

[Optimierungsgrad (SWIFT_OPTIMIZATION_LEVEL)]

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X