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.

628voto

drewag Punkte 91541

Laut dem sehr populären WWDC 2015 Vortrag Protokollorientiertes Programmieren 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, weil das Kopieren sicherer ist als das haben von mehreren Referenzen auf dasselbe Exemplar wie bei Klassen. Dies ist besonders wichtig, wenn eine Variable an viele Klassen übergeben wird und / oder in einer Multithreading-Umgebung. Wenn Sie immer eine Kopie Ihrer Variable an andere Stellen senden können, müssen Sie sich nie darum kümmern, dass an dieser anderen Stelle der Wert Ihrer Variable unter Ihnen geändert wird.

Mit Strukturen besteht viel weniger Sorge um Speicherlecks oder mehrere Threads, die auf ein einzelnes Exemplar einer Variablen zugreifen / es ändern. (Für die technisch Versierten, die Ausnahme davon tritt auf, wenn eine Struktur in einem Closure erfasst wird, denn dann erfasst sie tatsächlich eine Referenz auf das Exemplar, es sei denn, Sie markieren es explizit als Kopie).

Klassen können auch aufgebläht werden, weil eine Klasse nur von einer einzigen Superklasse erben kann. Das ermutigt uns, riesige Superklassen zu erstellen, die viele verschiedene Fähigkeiten beinhalten, 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 von Klassen zu beseitigen, um dieses Verhalten zu erreichen.

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

  • Kopieren oder Vergleichen von Exemplaren macht keinen Sinn (z.B. Fenster)
  • Die Lebensdauer des Exemplars ist mit externen Effekten verbunden (z.B. TemporäreDatei)
  • Exemplare sind nur "Senken" - schreibgeschützte Kanäle zum externen Zustand (z.B. CGContext)

Es wird angedeutet, dass Strukturen der Standard sein sollten und Klassen nur als Backup dienen sollten.

Auf der anderen Seite ist die Swift-Programmiersprache Dokumentation etwas widersprüchlich:

Struktur-Exemplare werden immer nach Wert übergeben, und Klassenexemplare werden immer nach Referenz übergeben. Das bedeutet, dass sie für unterschiedliche Aufgaben geeignet sind. Wenn Sie die Datenstrukturen und Funktionalitäten betrachten, die Sie für ein Projekt benötigen, entscheiden Sie, ob jede Datenstruktur als Klasse oder als Struktur definiert werden sollte.

Als allgemeine Richtlinie sollten Sie eine Struktur erstellen, wenn eine oder mehrere der folgenden Bedingungen zutreffen:

  • Der Hauptzweck der Struktur besteht darin, einige relativ einfache Datenwerte zu umfassen.
  • Es ist vernünftig zu erwarten, dass die umschlossenen Werte kopiert anstatt referenziert werden, wenn Sie ein Exemplar dieser Struktur zuweisen oder übergeben.
  • Alle Eigenschaften, die von der Struktur gespeichert werden, sind selbst Wertetypen, die auch erwartungsgemäß kopiert anstatt referenziert werden.
  • Die Struktur benötigt keine Vererbung von Eigenschaften oder Verhalten von einem anderen bestehenden Typ.

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

  • Die Größe einer geometrischen Form, die möglicherweise eine Breiteneigenschaft und eine Höheneigenschaft, beide vom Typ Double, umschließt.
  • Eine Möglichkeit, auf Bereiche innerhalb einer Serie zu verweisen, die möglicherweise eine Starteigenschaft und eine Längeneigenschaft, beide vom Typ Int, umschließt.
  • Ein Punkt in einem 3D-Koordinatensystem, der möglicherweise x-, y- und z-Eigenschaften, jeweils vom Typ Double, umschließt.

In allen anderen Fällen definieren Sie eine Klasse und erstellen Exemplare dieser Klasse, die verwaltet und nach Referenz übergeben werden. In der Praxis bedeutet dies, dass die meisten benutzerdefinierten Datenstrukturen Klassen und nicht Strukturen sein sollten.

Hier wird behauptet, dass wir standardmäßig Klassen verwenden sollten und Strukturen nur in spezifischen Situationen verwenden sollten. Letztendlich müssen Sie die realen Auswirkungen von Wertetypen und Referenztypen verstehen und dann eine informierte Entscheidung darüber treffen, wann Sie Strukturen oder Klassen verwenden sollen. Beachten Sie auch, dass diese Konzepte sich immer weiterentwickeln und die Dokumentation der Swift-Programmiersprache vor dem Protokollorientierten Programmierungs-Vortrag verfasst wurde.

15 Stimmen

@ElgsQianChen der ganze Sinn dieses Berichts ist, dass standardmäßig struct verwendet werden sollte und class nur bei Bedarf verwendet werden sollte. Structs sind viel sicherer und fehlerfrei, insbesondere in einer Multithreading-Umgebung. Ja, man kann immer eine Klasse anstelle eines structs verwenden, aber structs sind vorzuziehen.

21 Stimmen

@drewag Das scheint genau das Gegenteil von dem zu sein, was es aussagt. Es wurde gesagt, dass eine Klasse das Standard sein sollte, das du verwendest, nicht eine Struktur In der Praxis bedeutet dies, dass die meisten benutzerdefinierten Datentypen Klassen sein sollten, nicht Strukturen . Können Sie mir erklären, wie Sie nach dem Lesen davon zu dem Schluss kommen, dass die meisten Datensätze Strukturen und nicht Klassen sein sollten? Sie haben eine spezifische Reihe von Regeln gegeben, wann etwas eine Struktur sein sollte, und im Grunde gesagt "in allen anderen Szenarien ist eine Klasse besser."

2 Stimmen

@Matt, das ist wahr. Meine Sichtweise dazu ist, dass es spezifische Orte gibt, an denen ein Strukt verwendet werden kann, während eine Klasse überall verwendet werden kann, einschließlich anstelle von Strukturen. Das bedeutet für mich, dass Klassen eine Alternative sind, wenn Strukturen nicht ausreichen.

179voto

Khanh Nguyen Punkte 10964

Diese Antwort handelte ursprünglich von den Unterschieden in der Leistung zwischen Strukturen und Klassen. Leider gibt es zu viel Kontroverse um die Methode, die ich zum Messen verwendet habe. Ich habe sie unten gelassen, aber bitte interpretieren Sie sie nicht zu stark. Ich denke, nach all diesen Jahren ist es in der Swift-Community klar geworden, dass Strukturen (zusammen mit Enums) aufgrund ihrer Einfachheit und Sicherheit immer bevorzugt werden.

Wenn die Leistung für Ihre App wichtig ist, messen Sie es selbst. Ich denke immer noch, dass die Leistung von Strukturen in den meisten Fällen überlegen ist, aber die beste Antwort ist genau das, was jemand in den Kommentaren gesagt hat: Es kommt darauf an.

\=== ALTER 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 speziellen Anwendungsfall entscheiden.

Betrachten Sie das folgende Beispiel, das 2 Strategien zum Verpacken des Datentyps Int mit struct und class zeigt. Ich verwende 10 wiederholte Werte, um die Realität besser widerzuspiegeln, in der Sie mehrere Felder haben.

class Int10Klasse {
    let wert1, wert2, wert3, wert4, wert5, wert6, wert7, wert8, wert9, wert10: Int
    init(_ val: Int) {
        self.wert1 = val
        self.wert2 = val
        self.wert3 = val
        self.wert4 = val
        self.wert5 = val
        self.wert6 = val
        self.wert7 = val
        self.wert8 = val
        self.wert9 = val
        self.wert10 = val
    }
}

struct Int10Struktur {
    let wert1, wert2, wert3, wert4, wert5, wert6, wert7, wert8, wert9, wert10: Int
    init(_ val: Int) {
        self.wert1 = val
        self.wert2 = val
        self.wert3 = val
        self.wert4 = val
        self.wert5 = val
        self.wert6 = val
        self.wert7 = val
        self.wert8 = val
        self.wert9 = val
        self.wert10 = val
    }
}

func + (x: Int10Klasse, y: Int10Klasse) -> Int10Klasse {
    return Int10Klasse(x.wert + y.wert)
}

func + (x: Int10Struktur, y: Int10Struktur) -> Int10Struktur {
    return Int10Struktur(x.wert + y.wert)
}

Die Leistung wird gemessen mit

// Messen von Int10Klasse
messung("klasse (10 Felder)") {
    var x = Int10Klasse(0)
    for _ in 1...10000000 {
        x = x + Int10Klasse(1)
    }
}

// Messen von Int10Struktur
messung("struktur (10 Felder)") {
    var y = Int10Struktur(0)
    for _ in 1...10000000 {
        y = y + Int10Struktur(1)
    }
}

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

    block()

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

Der Code befindet sich unter https://github.com/knguyen2708/StructVsClassPerformance

UPDATE (27. März 2018):

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

  • Die class-Version dauerte 2,06 Sekunden
  • Die struct-Version dauerte 4,17e-08 Sekunden (50.000.000 Mal schneller)

(Ich führe nicht mehr Durchschnitte mehrerer Durchläufe durch, da Abweichungen sehr gering sind, unter 5%)

Anmerkung: Der Unterschied ist ohne ganze Moduloptimierung deutlich weniger dramatisch. Ich wäre dankbar, wenn jemand darauf hinweisen könnte, was die Flagge tatsächlich macht.


UPDATE (7. Mai 2016):

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

  • Die class-Version dauerte 2,159942142 Sekunden
  • Die struct-Version dauerte 5,83E-08 Sekunden (37.000.000 Mal schneller)

Anmerkung: Wie jemand erwähnte, dass in realen Szenarien wahrscheinlich mehr als 1 Feld in einer Struktur vorhanden sein 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):

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

Ab Swift 1.2, Xcode 6.3.2, Ausführung eines Release-Builds auf dem iPhone 5S, iOS 8.3, im Durchschnitt über 5 Durchläufe

  • Die class-Version dauerte 9,788332333 Sekunden
  • Die struct-Version dauerte 0,010532942 Sekunden (900 Mal schneller)

ALTEN ERGEBNISSE (aus unbekannter Zeit)

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

Bei einem Release-Build auf meinem MacBook Pro:

  • Die class-Version dauerte 1,10082 Sekunden
  • Die struct-Version dauerte 0,02324 Sekunden (50 Mal schneller)

28 Stimmen

Wahr, aber es scheint, dass das Kopieren einer Menge von Strukturen langsamer wäre als das Kopieren eines Verweises auf ein einzelnes Objekt. Mit anderen Worten ist es schneller, einen einzelnen Zeiger zu kopieren als einen beliebig großen Speicherblock.

3 Stimmen

In den meisten Fällen kopieren Sie nicht nur eine Referenz, sondern auch den Referenzzähler des Objekts sollte erhöht/verringert werden, was sich auf die Leistung auswirken kann.

14 Stimmen

-1 Dieser Test ist kein gutes Beispiel, weil es nur eine einzelne Variable in der Struktur gibt. Beachten Sie, dass die Strukturversion vergleichbar mit der Klassenversion wird, wenn Sie mehrere Werte und zwei Objekte hinzufügen. Je mehr Variablen Sie hinzufügen, desto langsamer wird die Strukturversion.

67voto

MadNik Punkte 7493

Ähnlichkeiten zwischen Strukturen und Klassen.

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

Und Unterschiede

1. Vererbung.

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

class Vehicle{
}

class Car : Vehicle{
}

Gehe für eine Klasse.

2. Übergeben von

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

Kontextuelle Unterschiede

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

Jetzt, wenn ich versuche, das x zu ändern, ist das 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 verwendet hätte, wäre dies ein gültiger Ausdruck. Denn in einer Klasse ist die unveränderliche Konstante die Referenz zur Klasse selbst, nicht zu ihren Instanzvariablen (sofern diese als Konstanten definiert wurden)

0 Stimmen

Sie können in Swift Structs vererben gist.github.com/AliSoftware/9e4946c8b6038572d678

13 Stimmen

Der obige Gist handelt davon, wie wir Vererbungsvarianten für Strukturen erreichen können. Sie werden Syntax wie sehen. A: B. Es handelt sich um eine Struktur namens A, die ein Protokoll namens B implementiert. Die Apple-Dokumentation erwähnt klar, dass Strukturen keine reine Vererbung unterstützen und es auch nicht tun.

2 Stimmen

Mann, dieser letzte Absatz von dir war großartig. Ich wusste immer, dass du Konstanten ändern konntest ... aber dann sah ich gelegentlich, wo du es nicht konntest, und war verwirrt. Diese Unterscheidung machte es sichtbar.

38voto

mfaani Punkte 28161

Unter der Annahme, dass wir wissen, dass Struct ein Werttyp ist und Class ein Referenztyp ist.

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

Basierend auf mikeash's Beitrag:

... Schauen wir uns zuerst einige extreme, offensichtliche Beispiele an. Ganzzahlen sind offensichtlich kopierbar. Sie sollten Werttypen sein. Netzwerk-Sockets können nicht sinnvoll kopiert werden. Sie sollten Referenztypen 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 Referenztyp sein.

Einige Typen können kopiert werden, aber es ist möglicherweise nicht immer erwünscht. Dies legt nahe, dass sie Referenztypen sein sollten. Zum Beispiel kann ein Button auf dem Bildschirm konzeptionell kopiert werden. Die Kopie wird nicht ganz identisch mit dem Original sein. Ein Klick auf die Kopie aktiviert nicht das Original. Die Kopie nimmt nicht denselben Platz auf dem Bildschirm ein. Wenn Sie den Button herumreichen oder in eine neue Variable setzen, möchten Sie wahrscheinlich auf den Original-Button verweisen, und Sie möchten nur eine Kopie machen, wenn dies ausdrücklich gewünscht wird. Das bedeutet, dass Ihr Button-Typ ein Referenztyp sein sollte.

View- und Fenster-Controller sind ein ähnliches Beispiel. Sie könnten theoretisch kopierbar sein, aber das ist fast nie das, was Sie tun möchten. Sie sollten Referenztypen sein.

Was ist mit Modelltypen? Sie könnten einen Benutzertyp haben, der einen Benutzer repräsentiert in Ihrem System, oder einen Krimityp, der eine von einem Benutzer durchgeführte Aktion darstellt. Diese sind ziemlich kopierbar, daher sollten sie wahrscheinlich Werttypen sein. Sie möchten jedoch wahrscheinlich, dass Aktualisierungen eines Verbrechens eines Benutzers an einer Stelle in Ihrem Programm für andere Teile des Programms sichtbar sind. Dies legt nahe, dass Ihre Benutzer von einer Art Benutzer Controller verwaltet werden sollten, der ein Referenztyp wäre. z.B.

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

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

Sammlungen sind ein interessanter Fall. Dazu gehören Arrays und Dictionaries sowie Strings. Sind sie kopierbar? Offensichtlich. Ist das Kopieren etwas, das Sie einfach und oft wollen? Das ist weniger klar.

Die meisten Sprachen sagen "nein" dazu und machen ihre Sammlungen zu Referenz Typen. Dies trifft auf Objective-C, Java, Python, JavaScript und fast jede andere Sprache zu, an die ich denken kann. (Eine große Ausnahme ist C++ mit STL-Sammlungstypen, aber C++ ist der unkontrollierbare Verrückte in der Sprachwelt, der alles seltsam macht.)

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

Ich persönlich benenne meine Klassen nicht so. Ich nenne meine normalerweise <em>UserManager</em> statt <em>UserController</em>, aber die Idee ist die gleiche

Zusätzlich verwenden Sie keinen class, wenn Sie jedes einzelne Vorkommen einer Funktion überschreiben müssen, d.h. wenn sie keine gemeinsame Funktionalität haben.

Verwenden Sie statt mehrerer 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 machen möchten. Mit Referenztypen können Sie das nicht von Haus aus tun. Bei Werttypen werden die Mutationen nicht geteilt.

3 Stimmen

Genau die Art von Erklärung, nach der ich gesucht habe. Schön geschrieben :)

0 Stimmen

Sehr hilfreiches Controller-Beispiel

1 Stimmen

@AskP Ich habe Mike persönlich per E-Mail kontaktiert und diesen zusätzlichen Code bekommen :)

30voto

Dan Rosenstark Punkte 66285

Hier sind einige andere Gründe zu beachten:

  1. structs erhalten einen automatischen Initialisierer, den man im Code überhaupt nicht pflegen muss.

    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 mehr Sie sie in Ihrem eigenen Code verwenden, desto mehr gewöhnen Sie sich daran, nach Wert anstatt nach Referenz zu übergeben. Beispielsweise:

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

5 Stimmen

Es ist wahr, du bekommst automatische Initialisierer. Du bekommst auch einen leeren Initialisierer, wenn alle Eigenschaften Optional sind. Wenn du jedoch eine Struktur in einem Framework hast, musst du den Initialisierer tatsächlich selbst schreiben, wenn du möchtest, dass er außerhalb des internal Bereichs verfügbar ist.

2 Stimmen

@Abizern bestätigt -- stackoverflow.com/a/26224873/8047 -- und Mann, das ist ärgerlich.

0 Stimmen

Ich erinnere mich daran, gelesen zu haben, dass es gute Gründe dafür gibt, also handelt es sich dabei um erwartetes Verhalten. Kann mich nicht genau daran erinnern, warum.

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