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?

625voto

drewag Punkte 91541

Basierend auf dem sehr beliebten WWDC 2015 Vortrag Protocol Oriented Programming in Swift (Video, Transkript), bietet Swift eine Reihe von Funktionen, die Strukturen in vielen Situationen besser als Klassen machen.

Strukturen sind vorzuziehen, wenn sie relativ klein und kopierbar sind, da das Kopieren viel sicherer ist als mehrere Verweise auf dieselbe Instanz zu haben, wie es bei Klassen der Fall ist. Dies ist besonders wichtig, wenn eine Variable an viele Klassen übergeben wird und/oder in einer Multithread-Umgebung. Wenn Sie immer eine Kopie Ihrer Variablen an andere Stellen senden können, müssen Sie sich nie darum kümmern, dass diese andere Stelle den Wert Ihrer Variablen unter Ihnen ändert.

Mit Strukturen gibt es viel weniger Sorgen um Speicherlecks oder mehrere Threads, die auf eine einzelne Instanz einer Variablen zugreifen/ diese ändern. (Für die technisch Interessierten, die Ausnahme davon tritt auf, wenn eine Struktur in einem Closure erfasst wird, denn dann erfasst es tatsächlich eine Referenz auf die Instanz, es sei denn, Sie kennzeichnen es explizit als kopiert).

Klassen können auch aufgebläht werden, da eine Klasse nur von einer einzigen Superklasse erben kann. Dies ermutigt uns dazu, riesige Superklassen zu erstellen, die viele verschiedene Fähigkeiten umfassen, die nur lose miteinander verbunden sind. Die Verwendung von Protokollen, insbesondere mit Protokollerweiterungen, bei denen Sie Implementierungen für Protokolle bereitstellen können, ermöglicht es Ihnen, die Notwendigkeit für Klassen zur Erzielung eines solchen Verhaltens zu beseitigen.

Der Vortrag stellt die Szenarien dar, in denen Klassen bevorzugt werden:

  • Kopieren oder Vergleichen von Instanzen macht keinen Sinn (z.B., Window)
  • Die Lebensdauer der Instanz ist mit externen Effekten verbunden (z.B., TemporaryFile)
  • Instanzen sind nur "Senken" - nur Schreib-Leitungen zum externen Zustand (z.B., CGContext)

Es wird angedeutet, dass Strukturen die Standardoption sein sollten und Klassen eine Ausweichmöglichkeit sein sollten.

Auf der anderen Seite ist die The Swift Programming Language Dokumentation etwas widersprüchlich:

Strukturinstanzen werden immer nach Wert übergeben, und Klasseninstanzen werden immer nach Referenz übergeben. Das bedeutet, dass sie sich für unterschiedliche Aufgaben eignen. Beim Betrachten der Datenkonstrukte und Funktionen, die Sie für ein Projekt benötigen, entscheiden Sie, ob jedes Datenkonstrukt als Klasse oder als Struktur definiert werden sollte.

Als allgemeine Richtlinie sollte eine Struktur erstellt werden, wenn eine oder mehrere dieser Bedingungen zutreffen:

  • Der Hauptzweck der Struktur besteht darin, einige relativ einfache Datenwerte zu kapseln.
  • Es ist vernünftig zu erwarten, dass die gekapselten Werte kopiert und nicht referenziert werden, wenn Sie eine Instanz dieser Struktur zuweisen oder übergeben.
  • Alle Eigenschaften, die von der Struktur gespeichert werden, sind selbst Werttypen, von denen erwartet wird, dass sie kopiert und nicht referenziert werden.
  • Die Struktur muss keine Eigenschaften oder Funktionen von einem anderen vorhandenen Typ erben.

Beispiele für gute Kandidaten für Strukturen sind:

  • Die Größe einer geometrischen Form, die vielleicht ein Eigenschaftenbreite und ein Höheneigenschaften, beide vom Typ Double, umfasst.
  • Ein Weg, um auf Bereiche innerhalb einer Serie zu verweisen, vielleicht Dashboards-Starteigenschaften und Längeigenschaften, beide vom Typ Int, umfassend.
  • Ein Punkt in einem 3D-Koordinatensystem, der vielleicht die x-, y- und z-Eigenschaften, jede vom Typ Double, umfasst.

In allen anderen Fällen sollte eine Klasse definiert und Instanzen dieser Klasse erstellt werden, die verwaltet und nach Referenz übergeben werden sollen. In der Praxis bedeutet dies, dass die meisten benutzerdefinierten Datenkonstrukte Klassen und nicht Strukturen sein sollten.

Hier wird behauptet, dass wir standardmäßig Klassen verwenden sollten und Strukturen nur in speziellen Situationen verwenden sollten. Letztendlich müssen Sie die realen Auswirkungen von Werttypen vs. Referenztypen verstehen und dann eine informierte Entscheidung darüber treffen, wann Sie Strukturen oder Klassen verwenden. Beachten Sie auch, dass diese Konzepte sich immer weiterentwickeln und die Dokumentation der Sprache Swift geschrieben wurde, bevor der Vortrag zur Protokollorientierten Programmierung gehalten wurde.

179voto

Khanh Nguyen Punkte 10964

Dies war ursprünglich eine Antwort über den Unterschied in der Leistung zwischen Struct und Klasse. Leider gibt es zu viel Kontroverse über die Methode, die ich für die Messung verwendet habe. Ich habe sie unten gelassen, aber bitte interpretieren Sie das nicht zu sehr. Ich denke, nach all diesen Jahren ist es in der Swift-Community klar geworden, dass Struct (zusammen mit Enum) aufgrund seiner Einfachheit und Sicherheit immer bevorzugt wird.

Wenn die Leistung für Ihre App wichtig ist, messen Sie sie selbst. Ich denke immer noch, dass die Leistung von Struct in den meisten Fällen überlegen ist, aber die beste Antwort ist genau wie jemand in den Kommentaren sagte: es hängt davon ab.

\=== ALTE ANTWORT ===

Da Strukturinstanzen auf dem Stapel allokiert werden und Klasseninstanzen auf dem Heap allokiert werden, können Strukturen manchmal drastisch schneller sein.

Sie sollten jedoch immer selbst messen und basierend auf Ihrem einzigartigen Anwendungsfall entscheiden.

Betrachten Sie das folgende Beispiel, das 2 Strategien zum Verpacken des Datentyps "Int" mit "Struct" und "Klasse" zeigt. Ich verwende 10 wiederholte Werte, um die realen Bedingungen besser darzustellen, bei denen Sie mehrere Felder haben.

Klasse Int10Klasse {
    lassen Wert1, Wert2, Wert3, Wert4, Wert5, Wert6, Wert7, Wert8, Wert9, Wert10: 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
    }
}

Struktur Int10Struktur {
    lassen Wert1, Wert2, Wert3, Wert4, Wert5, Wert6, Wert7, Wert8, Wert9, Wert10: 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: Int10Klasse, y: Int10Klasse) -> Int10Klasse {
    zurück IntClass(x.value + y.value)
}

func + (x: Int10Struktur, y: Int10Struktur) -> Int10Struktur {
    zurück IntStruct(x.value + y.value)
}

Die Leistung wird gemessen mit

// Messen Sie Int10Klasse
Messung("Klasse (10 Felder)") {
    var x = Int10Klasse(0)
    für _ in 1...10000000 {
        x = x + Int10Klasse(1)
    }
}

// Messen Sie Int10Struktur
Messung("Struct (10 Felder)") {
    var y = Int10Struktur(0)
    für _ in 1...10000000 {
        y = y + Int10Struktur(1)
    }
}

func messen(name: Zeichenfolge, @noescape block: () -> ()) {
    lassen t0 = CACurrentMediaTime()

    block()

    lassen dt = CACurrentMediaTime() - t0
    drucken("{name} -> {dt}")
}

Der Code ist hier zu finden https://github.com/knguyen2708/StructVsClassPerformance

UPDATE (27. März 2018):

Ab Swift 4.0, Xcode 9.2, Ausführung des Release-Builds auf dem iPhone 6S, iOS 11.2.6, Swift Compiler-Einstellung ist -O -whole-module-optimization:

  • Klasse Version dauerte 2,06 Sekunden
  • Struct Version dauerte 4.17e-08 Sekunden (50.000.000 Mal schneller)

(Ich führe nicht mehr einen Durchschnitt mehrerer Läufe durch, da die Abweichungen sehr klein sind, unter 5%)

Hinweis: Der Unterschied ist ohne Whole-Module-Optimierung viel weniger dramatisch. Ich wäre froh, wenn mir jemand sagen könnte, was die Flagge tatsächlich bewirkt.


UPDATE (7. Mai 2016):

Ab Swift 2.2.1, Xcode 7.3, Ausführung des Release-Builds auf dem iPhone 6S, iOS 9.3.1, gemittelt über 5 Läufe, Swift Compiler-Einstellung ist -O -whole-module-optimization:

  • Klasse Version dauerte 2,159942142s
  • Struct Version dauerte 5,83E-08s (37.000.000 Mal schneller)

Hinweis: Da jemand darauf hingewiesen hat, dass es in realen Szenarien wahrscheinlich mehr als 1 Feld in einer Struktur geben wird, habe ich Tests für Strukturen / Klassen mit 10 Feldern anstelle von 1 hinzugefügt. Überraschenderweise variieren die Ergebnisse nicht viel.


URSPRÜNGLICHE ERGEBNISSE (1. Juni 2014):

(Ausgeführt auf Struktur / Klasse mit 1 Feld, nicht 10)

Ab Swift 1.2, Xcode 6.3.2, Ausführung des Release-Builds auf dem iPhone 5S, iOS 8.3, gemittelt über 5 Läufe

  • Klasse Version dauerte 9,788332333s
  • Struct Version dauerte 0,010532942s (900 Mal schneller)

ALTE ERGEBNISSE (aus unbekannter Zeit)

(Ausgeführt auf Struktur / Klasse mit 1 Feld, nicht 10)

Mit Release-Build auf meinem MacBook Pro:

  • Die Klasse Version dauerte 1,10082 Sekunden
  • Die Struct Version dauerte 0,02324 Sekunden (50 Mal schneller)

65voto

MadNik Punkte 7493

Ähnlichkeiten zwischen Strukturen und Klassen.

Ich habe ein Gist dafür mit einfachen Beispielen erstellt. https://github.com/objc-swift/swift-classes-vs-structures

Und Unterschiede

1. Vererbung.

Strukturen können in Swift nicht vererben. Wenn du möchtest

class Vehicle{
}

class Car : Vehicle{
}

Verwende eine Klasse.

2. Übergabe von

Swift-Strukturen werden nach Wert übergeben und Klasseninstanzen nach Referenz.

Kontextuelle Unterschiede

Strukturenkonstanten und Variablen

Beispiel (Verwendet auf der WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Definiert eine Struktur namens Point.

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

Wenn ich jetzt versuche, das x zu ändern. Dies ist ein gültiger Ausdruck.

point.x = 5

Aber wenn ich einen Punkt als Konstante definiert habe.

let point = Point(x:0.0,y:2.0)
point.x = 5 //Dies führt zu einem Kompilierungsfehler.

In diesem Fall ist der gesamte Punkt eine unveränderliche Konstante.

Wenn ich eine Klasse Point verwende, ist dies ein gültiger Ausdruck. Denn in einer Klasse ist die unveränderliche Konstante die Referenz auf die Klasse selbst und nicht auf deren Instanzvariablen (Sofern diese Variablen als Konstanten definiert wurden)

38voto

mfaani Punkte 28161

Angenommen, wir wissen, dass Struct ein Werttyp und Klasse ein Verweistyp ist.

Wenn Sie nicht wissen, was ein Werttyp und ein Verweistyp sind, lesen Sie <a href="https://stackoverflow.com/a/36208432/5175709">Was ist der Unterschied zwischen Übergabe per Referenz und Übergabe per Wert?</a>

Basierend auf mikeash's Beitrag:

... Sehen wir uns zuerst einige extreme, offensichtliche Beispiele an. Ganzzahlen sind offensichtlich kopierbar. Sie sollten Werttypen sein. Netzwerksockets können nicht sinnvoll kopiert werden. Sie sollten Verweistypen sein. Punkte, wie in x, y-Paaren, sind kopierbar. Sie sollten Werttypen sein. Ein Controller, der eine Festplatte repräsentiert, kann nicht sinnvoll kopiert werden. Das sollte ein Verweistyp sein.

Einige Typen können zwar kopiert werden, aber dies ist möglicherweise nicht etwas, was Sie immer wollen. Dies legt nahe, dass sie Verweistypen sein sollten. Zum Beispiel kann eine Schaltfläche auf dem Bildschirm konzeptionell kopiert werden. Die Kopie wird nicht ganz identisch mit dem Original sein. Ein Klick auf die Kopie wird das Original nicht aktivieren. Die Kopie wird nicht denselben Ort auf dem Bildschirm einnehmen. Wenn Sie die Schaltfläche weitergeben oder in eine neue Variable setzen, möchten Sie wahrscheinlich auf die Originalschaltfläche verweisen, und Sie möchten nur dann eine Kopie erstellen, wenn dies ausdrücklich angefordert wird. Das bedeutet, dass Ihr Schaltflächentyp ein Verweistyp sein sollte.

Ansichts- und Fenstercontroller sind ein ähnliches Beispiel. Sie könnten kopierbar sein, theoretisch, aber das ist fast nie das, was Sie tun möchten. Sie sollten Verweistypen sein.

Was ist mit Modeltypen? Sie könnten einen Benutzertyp haben, der einen Benutzer in Ihrem System darstellt, oder einen Kriminalitätstyp, der eine von einem Benutzer durchgeführte Aktion darstellt. Diese sind ziemlich kopierbar, sodass sie wahrscheinlich Werttypen sein sollten. Sie möchten jedoch wahrscheinlich, dass Updates für eine Benutzerkriminalität, die an einer Stelle in Ihrem Programm vorgenommen wurden, in anderen Teilen des Programms sichtbar sind. Dies legt nahe, dass Ihre Benutzer von einer art Benutzercontroller verwaltet werden sollten, der ein Verweistyp sein würde. z.B.

struct User {}
class BenutzerController {
    var benutzer: [User]

    func add(benutzer: User) { ... }
    func remove(benutzerMitName: String) { ... }
    func ...
}

Sammlungen sind ein interessanter Fall. Dazu gehören Dinge wie Arrays und Dictionaries sowie Zeichenfolgen. Sind sie kopierbar? Offensichtlich. Soll das Kopieren etwas sein, das leicht und oft passiert? Das ist weniger klar.

Die meisten Sprachen sagen "nein" dazu und machen ihre Sammlungen zu Verweistypen. Das gilt für Objective-C, Java, Python, JavaScript und fast jede andere Sprache, an die ich denken kann. (Eine große Ausnahme ist C++ mit STL-Sammlungstypen, aber C++ ist der Wahnsinnige der Sprachenwelt, der alles merkwürdig macht.)

Swift sagt "ja", was bedeutet, dass Typen wie Array, Dictionary und String Strukturen anstelle von Klassen sind. Sie werden beim Zuweisen und beim Übergeben als Parameter kopiert. Das ist eine durchaus vernünftige Wahl, solange die Kopie günstig ist, was Swift sehr zu erreichen versucht. ...

Ich benenne meine Klassen persönlich nicht so. Ich benenne meine normalerweise <em>BenutzerManager</em> anstelle von <em>BenutzerController</em>, aber die Idee ist die gleiche

Verwenden Sie außerdem nicht die Klasse, wenn Sie jedes einzelne Vorkommen einer Funktion überschreiben müssen, d.h. sie haben keine gemeinsame Funktionalität.

Verwenden Sie also anstelle von mehreren Unterklassen einer Klasse mehrere Strukturen, die einem Protokoll entsprechen.


Ein weiterer vernünftiger Fall für Strukturen ist, wenn Sie einen Delta-/Unterschied zwischen Ihrem alten und neuen Modell erstellen möchten. Mit Verweistypen ist das nicht einfach möglich. Bei Werttypen werden die Mutationen nicht geteilt.

30voto

Dan Rosenstark Punkte 66285

Hier sind einige weitere Gründe zu erwägen:

  1. Strukturen erhalten einen automatischen Initialisierer, den Sie im Code überhaupt nicht pflegen müssen.

    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")

Um das in einer Klasse zu bekommen, müssten Sie den Initialisierer hinzufügen und pflegen...

  1. Grundlegende Sammlungstypen wie Array sind Strukturen. Je häufiger Sie sie in Ihrem eigenen Code verwenden, desto mehr werden Sie sich daran gewöhnen, nach Wert anstatt nach Referenz zu übergeben. Zum Beispiel:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Scheinbar ist Unveränderlichkeit vs. Veränderlichkeit ein großes Thema, aber viele kluge Leute denken, dass Unveränderlichkeit -- Strukturen in diesem Fall -- bevorzugt wird. Mutierbare vs. unveränderliche Objekte

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