568 Stimmen

Warum Struktur über Klasse wählen?

Beim Experimentieren mit Swift, kommend aus einer Java-Hintergrund, warum möchtest du eine Struktur anstelle einer Klasse wählen? Es scheint, dass sie dasselbe sind, wobei eine Struktur weniger Funktionalität bietet. Warum sollte man sie dann wählen?

13 Stimmen

Strukturen werden immer kopiert, wenn sie in Ihrem Code weitergereicht werden und verwenden kein Referenzzählen. Quelle: developer.apple.com/library/prerelease/ios/documentation/swi‌​ft/…

4 Stimmen

Ich würde sagen, dass structs besser geeignet sind, um Daten zu speichern, nicht Logik. Um es in Java-Begriffen auszudrücken, stelle dir structs als "Value Objects" vor.

6 Stimmen

Ich bin erstaunt, dass im gesamten Gespräch nicht direkt auf copy-on-write oder lazy copy verwiesen wird. Eventuelle Bedenken in Bezug auf die Leistung beim Strukturkopieren sind aufgrund dieses Designs größtenteils obsolet.

22voto

Catfish_Man Punkte 40261

Einige Vorteile:

  • automatisch threadsicher aufgrund der Nichtteilbarkeit
  • verwendet weniger Speicher aufgrund des Fehlens von isa und refcount (und tatsächlich in der Regel auf dem Stack allokiert)
  • Methoden sind immer statisch versendet und können daher eingebettet werden (obwohl @final dies für Klassen tun kann)
  • einfacher zu verstehen (keine Notwendigkeit für "defensive Kopien", wie es üblich ist bei NSArray, NSString, usw...) aus dem gleichen Grund wie die Threadsicherheit

0 Stimmen

Nicht sicher, ob es außerhalb des Umfangs dieser Antwort liegt, aber können Sie den Punkt "Methoden werden immer statisch weitergeleitet" erklären (oder verlinken, denke ich)?

3 Stimmen

Sicher. Ich kann auch eine Einschränkung hinzufügen. Der Zweck der dynamischen Disposition besteht darin, eine Implementierung zu wählen, wenn Sie nicht im Voraus wissen, welche verwendet werden soll. In Swift kann dies entweder aufgrund von Vererbung (kann in einer Unterklasse überschrieben werden) oder aufgrund der Generizität der Funktion sein (Sie wissen nicht, welcher generische Parameter verwendet wird). Strukturen können nicht vererbt werden und die Optimierung des gesamten Moduls sowie die generische Spezialisierung beseitigen größtenteils die unbekannten Generika, sodass Methoden direkt aufgerufen werden können, anstatt herauszufinden, was aufgerufen werden soll. Nichtspezialisierte Generika führen jedoch immer noch dynamische Zuweisung für Strukturen durch.

1 Stimmen

Vielen Dank, großartige Erklärung. Erwarten wir also mehr Laufzeitgeschwindigkeit oder weniger Unklarheit aus der Sicht eines IDE oder beides?

18voto

casillas Punkte 15671

Structs sind Value-Typen und Klassen sind Referenz-Typen

  • Value-Typen sind schneller als Referenz-Typen
  • Value-Typ-Instanzen sind in einer Multi-Thread-Umgebung sicher, da mehrere Threads die Instanz mutieren können, ohne sich um Rennbedingungen oder Deadlocks kümmern zu müssen
  • Value-Typen haben keine Referenzen im Gegensatz zu Referenz-Typen; daher gibt es keine Speicherlecks.

Verwenden Sie einen Value-Typ, wenn:

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

Verwenden Sie einen Referenz-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

Schwift-Value-Typen werden im Stack aufbewahrt. In einem Prozess hat jeder Thread seinen eigenen Stack-Speicher, sodass kein anderer Thread auf Ihren Werttyp direkt zugreifen kann. Daher keine Rennbedingungen, Sperren, Deadlocks oder komplexe Thread-Synchronisierung.

Value-Typen benötigen keine dynamische Speicherzuweisung oder Referenzzählung, die beide teure Operationen sind. Gleichzeitig sind Methoden von Value-Typen statisch verteilt. Dies schafft einen enormen Vorteil für Value-Typen in Bezug auf die Leistung.

Zur Erinnerung hier eine Liste der Swift

Value-Typen:

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

Referenz-Typen:

  • Klasse
  • Alles, was von NSObject kommt
  • Funktion
  • Schließung

0 Stimmen

NICHT sicher in einer Mehrfach-Thread-Umgebung Wenn die Instanz, die als Objekt übergeben wird, von mehreren Threads gleichzeitig zugegriffen wird, kann diese Funktion immer noch true zurückgeben. Daher dürfen Sie diese Funktion nur aus mutierenden Methoden mit geeigneter Thread-Synchronisierung aufrufen. Das stellt sicher, dass isKnownUniquelyReferenced(_:) nur true zurückgibt, wenn es wirklich nur einen Zugriffsgeber gibt oder wenn es zu einem Wettlauf kommt, was bereits ein undefiniertes Verhalten ist.

14voto

Manoj Karki Punkte 270

Struktur ist viel schneller als Klasse. Außerdem, wenn Sie Vererbung benötigen, dann 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, die Beschreibung, das 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 ausdrucken, erhalten wir

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

Hier können wir sehen, dass die id und Beschreibung von FlightA geändert sind, 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 ausdrucken, erhalten wir

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

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

2 Stimmen

Sie haben nie eine Instanz von FLightB erstellt

1 Stimmen

Dann warum sprechen Sie über FlightB, Bro? Hier können wir sehen, dass die FlightB-Instanz nicht geändert wird

0 Stimmen

@ManojKarki, tolle Antwort. Ich wollte nur darauf hinweisen, dass du flightA zweimal deklariert hast, wenn du vermutlich FlightA, dann FlightB deklarieren wolltest.

7voto

David James Punkte 2242

Wenn man die Frage aus der Perspektive von Werttypen vs. Referenztypen betrachtet, würde es laut diesem Apple-Blog-Beitrag sehr einfach erscheinen:

Verwende einen Werttyp [z. B. Struktur, Aufzählung], wenn:

  • Das Vergleichen von Instanzdaten mit == sinnvoll ist
  • Du Kopien mit unabhängigem Zustand haben möchtest
  • Die Daten im Code über mehrere Threads hinweg verwendet werden sollen

Verwende einen Referenztyp [z. B. Klasse], wenn:

  • Das Vergleichen der Instanzenidentität mit === sinnvoll ist
  • Du gemeinsamen, veränderlichen Zustand erstellen möchtest

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

6voto

yoAlex5 Punkte 20661

Struktur vs. Klasse

[Stack vs. Heap]
[Wert vs. Referenztyp]

Struct ist eher bevorzugt. Aber Struct löst nicht standardmäßig alle Probleme. Normalerweise hört man, dass ein Werttyp auf dem Stack zugewiesen ist, aber das ist nicht immer wahr. Nur lokale Variablen werden auf dem Stack zugewiesen

//einfache Blöcke
struct Werttyp {}
class Referenztyp {}

struct StructMitRef {
    let ref1 = Referenztyp()
}

class KlasseMitRef {
    let ref1 = Referenztyp()
}

func foo() {

    //einfache Blöcke
    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 sich nicht auf retainCount verlassen, da es keine nützlichen Informationen liefert

Um den Stack oder den Heap zu überprüfen

Während des Kompilierens kann Swift Intermediate Language (SIL) Ihren Code optimieren

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

//emit-silgen -> emit-sil(wird in jedem Fall verwendet)
//-emit-silgen           Roh-SIL-Datei(en) ausgeben
//-emit-sil              Kanonische SIL-Datei(en) ausgeben
//Optimierung: O, Osize, Onone. Gleiches wie Swift Compiler - Code Generation -> Optimierungsstufe

Dort können Sie alloc_stack (Zuweisung auf dem Stack) und alloc_box (Zuweisung auf dem Heap) finden

[Optimierungsstufe (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