Warum Struktur statt Klasse wählen?


476

Wenn Sie mit Swift aus Java spielen, warum sollten Sie eine Struktur anstelle einer Klasse wählen? Es scheint, als wären sie dasselbe, da eine Struktur weniger Funktionalität bietet. Warum dann wählen?


11
Strukturen werden immer kopiert, wenn sie in Ihrem Code weitergegeben werden, und verwenden keine Referenzzählung. Quelle: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex

4
Ich würde sagen, dass Strukturen eher dazu geeignet sind, Daten zu speichern, als Logik. Stellen Sie sich Strukturen als "Wertobjekte" vor, um in Java zu sprechen.
Vincent Guerci

6
Ich bin in diesem ganzen Gespräch erstaunt es keine direkte Erwähnung von copy-on-write aka faul Kopie . Bedenken hinsichtlich der Leistung von Strukturkopien sind aufgrund dieses Entwurfs größtenteils umstritten.
David James

3
Die Wahl einer Struktur gegenüber einer Klasse ist keine Ansichtssache. Es gibt bestimmte Gründe, sich für den einen oder anderen zu entscheiden.
David James

Ich empfehle dringend zu sehen, warum Array nicht threadSafe ist . Dies hängt zusammen, da Arrays & Structs beide Werttypen sind. Alle Antworten hier erwähnen, dass bei Strukturen / Arrays / Werttypen niemals ein Thread-Sicherheitsproblem auftreten wird, aber es gibt einen Eckfall, in dem Sie dies tun werden.
Honig

Antworten:


548

Laut dem sehr beliebten WWDC 2015 Talk Protocol Oriented Programming in Swift ( Video , Transkript ) bietet Swift eine Reihe von Funktionen, mit denen Strukturen unter vielen Umständen besser als Klassen sind.

Strukturen sind vorzuziehen, wenn sie relativ klein und kopierbar sind, da das Kopieren viel sicherer ist als mehrere Verweise auf dieselbe Instanz wie bei Klassen. Dies ist besonders wichtig, wenn eine Variable an viele Klassen und / oder in einer Multithread-Umgebung weitergegeben wird. Wenn Sie immer eine Kopie Ihrer Variablen an andere Orte senden können, müssen Sie sich keine Sorgen machen, dass dieser andere Ort den Wert Ihrer Variablen unter Ihnen ändert.

Mit Structs müssen Sie sich weniger Gedanken über Speicherverluste oder mehrere Threads machen, die auf eine einzelne Instanz einer Variablen zugreifen oder diese ändern möchten. (Für technisch Interessierte besteht die Ausnahme darin, dass eine Struktur innerhalb eines Abschlusses erfasst wird, da dann tatsächlich ein Verweis auf die Instanz erfasst wird, es sei denn, Sie markieren ihn ausdrücklich als zu kopierend.)

Klassen können auch aufgebläht werden, da eine Klasse nur von einer einzelnen Oberklasse erben kann. Das ermutigt uns, riesige Superklassen zu erstellen, die viele verschiedene Fähigkeiten umfassen, die nur lose miteinander verbunden sind. Durch die Verwendung von Protokollen, insbesondere mit Protokollerweiterungen, bei denen Sie Implementierungen für Protokolle bereitstellen können, können Sie die Notwendigkeit von Klassen beseitigen, um diese Art von Verhalten zu erreichen.

Der Vortrag beschreibt diese Szenarien, in denen Klassen bevorzugt werden:

  • Das Kopieren oder Vergleichen von Instanzen ist nicht sinnvoll (z. B. Fenster).
  • Die Lebensdauer der Instanz ist an externe Effekte gebunden (z. B. TemporaryFile).
  • Instanzen sind nur "Senken" - Nur-Schreib-Conduits zum externen Status (zB CGContext)

Dies impliziert, dass Strukturen die Standardeinstellung sein sollten und Klassen ein Fallback sein sollten.

Andererseits ist die Dokumentation zur Swift-Programmiersprache etwas widersprüchlich:

Strukturinstanzen werden immer als Wert übergeben, und Klasseninstanzen werden immer als Referenz übergeben. Dies bedeutet, dass sie für verschiedene Arten von Aufgaben geeignet sind. Wenn Sie die Datenkonstrukte und Funktionen berücksichtigen, die Sie für ein Projekt benötigen, entscheiden Sie, ob jedes Datenkonstrukt als Klasse oder als Struktur definiert werden soll.

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

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

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

  • Die Größe einer geometrischen Form, die möglicherweise eine width-Eigenschaft und eine height-Eigenschaft vom Typ Double enthält.
  • Eine Möglichkeit, auf Bereiche innerhalb einer Reihe zu verweisen, die möglicherweise eine Starteigenschaft und eine Längeneigenschaft vom Typ Int.
  • Ein Punkt in einem 3D-Koordinatensystem, der möglicherweise die Eigenschaften x, y und z vom Typ Double enthält.

In allen anderen Fällen definieren Sie eine Klasse und erstellen Sie Instanzen dieser Klasse, die verwaltet und als Referenz übergeben werden sollen. In der Praxis bedeutet dies, dass die meisten benutzerdefinierten Datenkonstrukte Klassen und keine Strukturen sein sollten.

Hier wird behauptet, dass wir standardmäßig Klassen verwenden und Strukturen nur unter bestimmten Umständen verwenden sollten. Letztendlich müssen Sie die realen Auswirkungen von Werttypen im Vergleich zu Referenztypen verstehen und dann eine fundierte Entscheidung darüber treffen, wann Strukturen oder Klassen verwendet werden sollen. Denken Sie auch daran, dass sich diese Konzepte ständig weiterentwickeln und die Dokumentation zur Swift Programming Language geschrieben wurde, bevor der Vortrag über die protokollorientierte Programmierung gehalten wurde.


12
@ElgsQianChen der springende Punkt dieser Beschreibung ist, dass struct standardmäßig ausgewählt und class nur bei Bedarf verwendet werden sollte. Strukturen sind viel sicherer und fehlerfrei, insbesondere in einer Multithread-Umgebung. Ja, Sie können immer eine Klasse anstelle einer Struktur verwenden, aber Strukturen sind vorzuziehen.
Drawag

16
@drewag Das scheint genau das Gegenteil von dem zu sein, was es sagt. Es wurde gesagt, dass eine Klasse der Standard sein sollte, den Sie verwenden, nicht eine Struktur. In practice, this means that most custom data constructs should be classes, not structures.Können Sie mir erklären, wie Sie nach dem Lesen erhalten, dass die meisten Datensätze Strukturen und keine Klassen sein sollten? Sie gaben ein bestimmtes Regelwerk an, wenn etwas eine Struktur sein sollte, und sagten so ziemlich "alle anderen Szenarien, in denen eine Klasse besser ist".
Matt

42
Die letzte Zeile sollte lauten: "Mein persönlicher Rat ist das Gegenteil der Dokumentation:" ... und dann ist es eine großartige Antwort!
Dan Rosenstark

5
In Swift 2.2 heißt es immer noch, in den meisten Situationen Klassen zu verwenden.
David James

6
Struktur über Klasse reduziert definitiv die Komplexität. Was bedeutet dies für die Speichernutzung, wenn Strukturen zur Standardauswahl werden? Wenn Dinge überall kopiert werden, anstatt sie zu referenzieren, sollte dies die Speichernutzung durch die App erhöhen. Sollte es nicht?
MadNik

164

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

Sie sollten es jedoch immer selbst messen und anhand Ihres speziellen Anwendungsfalls entscheiden.

Betrachten Sie das folgende Beispiel, das zwei Strategien zum Umschließen des IntDatentyps mit structund demonstriert class. Ich verwende 10 wiederholte Werte, um die reale Welt, in der Sie mehrere Felder haben, besser widerzuspiegeln.

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

Die Leistung wird mit gemessen

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

Code finden Sie unter https://github.com/knguyen2708/StructVsClassPerformance

UPDATE (27. März 2018) :

Ab Swift 4.0, Xcode 9.2, mit Release Build auf iPhone 6S, iOS 11.2.6, ist die Swift Compiler-Einstellung -O -whole-module-optimization:

  • class Version dauerte 2,06 Sekunden
  • struct Version dauerte 4.17e-08 Sekunden (50.000.000 mal schneller)

(Ich habe keine durchschnittlichen Mehrfachläufe mehr, da die Abweichungen mit unter 5% sehr gering sind.)

Hinweis : Der Unterschied ist ohne Optimierung des gesamten Moduls viel weniger dramatisch. Ich würde mich freuen, wenn jemand darauf hinweisen kann, was die Flagge tatsächlich tut.


UPDATE (7. Mai 2016) :

Ab Swift 2.2.1, Xcode 7.3, mit Release Build auf iPhone 6S, iOS 9.3.1, gemittelt über 5 Läufe, lautet die Swift Compiler-Einstellung -O -whole-module-optimization:

  • class Version dauerte 2.159942142s
  • struct Version dauerte 5.83E-08s (37.000.000 mal schneller)

Hinweis : Da jemand erwähnt hat, dass in realen Szenarien wahrscheinlich mehr als ein Feld in einer Struktur vorhanden ist, habe ich Tests für Strukturen / Klassen mit 10 statt 1 Feldern hinzugefügt. Überraschenderweise variieren die Ergebnisse nicht stark.


URSPRÜNGLICHE ERGEBNISSE (1. Juni 2014):

(Lief auf Struktur / Klasse mit 1 Feld, nicht 10)

Ab Swift 1.2, Xcode 6.3.2, mit Release Build auf iPhone 5S, iOS 8.3, durchschnittlich über 5 Läufe

  • class Version dauerte 9.788332333s
  • struct Version dauerte 0.010532942s (900 mal schneller)

ALTE ERGEBNISSE (aus unbekannter Zeit)

(Lief auf Struktur / Klasse mit 1 Feld, nicht 10)

Mit Release Build auf meinem MacBook Pro:

  • Die classVersion dauerte 1.10082 Sekunden
  • Die structVersion dauerte 0,02324 Sekunden (50-mal schneller)

27
Stimmt, aber es scheint, dass das Kopieren einer Reihe von Strukturen langsamer ist als das Kopieren eines Verweises auf ein einzelnes Objekt. Mit anderen Worten, es ist schneller, einen einzelnen Zeiger zu kopieren, als einen beliebig großen Speicherblock zu kopieren.
Tylerc230

14
-1 Dieser Test ist kein gutes Beispiel, da die Struktur nur eine einzige Variable enthält. Beachten Sie, dass die Strukturversion mit der Klassenversion vergleichbar wird, wenn Sie mehrere Werte und ein oder zwei Objekte hinzufügen. Je mehr Variablen Sie hinzufügen, desto langsamer wird die Strukturversion.
Joshrl

6
@joshrl hat verstanden, aber ein Beispiel ist "gut" oder hängt nicht von der jeweiligen Situation ab. Dieser Code wurde aus meiner eigenen App extrahiert, ist also ein gültiger Anwendungsfall, und die Verwendung von Strukturen hat die Leistung meiner App massiv verbessert. Es ist nur wahrscheinlich kein häufiger Anwendungsfall (der häufigste Anwendungsfall ist, dass sich bei den meisten Apps niemand darum kümmert, wie schnell Daten weitergegeben werden können, da der Engpass an einem anderen Ort auftritt, z. B. bei Netzwerkverbindungen. Optimierung ist das sowieso nicht kritisch, wenn Sie GHz-Geräte mit GB oder RAM haben).
Khanh Nguyen

26
Soweit ich weiß, ist das schnelle Kopieren so optimiert, dass es zum Zeitpunkt des Schreibens erfolgt. Dies bedeutet, dass keine physische Speicherkopie erstellt wird, es sei denn, die neue Kopie wird geändert.
Matjan

6
Diese Antwort zeigt ein äußerst triviales Beispiel, das unrealistisch und daher in vielen Fällen falsch ist. Eine bessere Antwort wäre "es kommt darauf an".
Ich wurde

60

Ähnlichkeiten zwischen Strukturen und Klassen.

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

Und Unterschiede

1. Vererbung.

Strukturen können nicht schnell erben. Falls Sie es wollen

class Vehicle{
}

class Car : Vehicle{
}

Geh in eine Klasse.

2. Vorbeifahren

Schnelle Strukturen werden als Wert und Klasseninstanzen als Referenz übergeben.

Kontextunterschiede

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

Nun, wenn ich versuche, das x zu ändern. Es ist ein gültiger Ausdruck.

point.x = 5

Aber wenn ich einen Punkt als konstant definiert habe.

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

In diesem Fall ist der gesamte Punkt unveränderlich konstant.

Wenn ich stattdessen einen Klassenpunkt verwendet habe, ist dies ein gültiger Ausdruck. Weil in einer Klasse unveränderliche Konstante der Verweis auf die Klasse selbst nicht ihre Instanzvariablen ist (es sei denn, diese Variablen sind als Konstanten definiert)



12
Im obigen Kern geht es darum, wie wir Vererbungsaromen für struct erreichen können. Sie werden Syntax wie sehen. A: B. Es heißt struct A implementiert ein Protokoll namens B. In der Apple-Dokumentation wird deutlich erwähnt, dass struct keine reine Vererbung unterstützt und dies auch nicht.
MadNik

2
Mann, der letzte Absatz von dir war großartig. Ich wusste immer, dass man Konstanten ändern kann ... aber dann sah ich von Zeit zu Zeit, wo man es nicht konnte, also war ich verblüfft. Diese Unterscheidung machte es sichtbar
Honey

28

Hier sind einige andere Gründe zu berücksichtigen:

  1. Strukturen erhalten einen automatischen Initialisierer, den Sie überhaupt nicht im Code 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 dies in einer Klasse zu erhalten, müssten Sie den Initialisierer hinzufügen und den Intializer beibehalten ...

  1. Grundlegende Sammlungstypen wie Arraysind Strukturen. Je häufiger Sie sie in Ihrem eigenen Code verwenden, desto mehr werden Sie sich daran gewöhnen, Werte anstelle von Referenzen 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. Anscheinend ist Unveränderlichkeit vs. Veränderlichkeit ein großes Thema, aber viele kluge Leute halten Unveränderlichkeit - in diesem Fall Strukturen - für vorzuziehen. Veränderliche gegen unveränderliche Objekte


4
Es ist wahr, Sie erhalten automatische Initialisierer. Sie erhalten auch einen leeren Initialisierer, wenn alle Eigenschaften optional sind. Wenn Sie jedoch eine Struktur in einem Framework haben, müssen Sie den Initialisierer tatsächlich selbst schreiben, wenn Sie möchten, dass er außerhalb des internalGültigkeitsbereichs verfügbar ist .
Abizern

2
@Abizern bestätigt - stackoverflow.com/a/26224873/8047 - und Mann, der nervt.
Dan Rosenstark

2
@Abizern Es gibt gute Gründe für alles in Swift, aber jedes Mal, wenn etwas an einem Ort wahr ist und nicht an einem anderen, muss der Entwickler mehr wissen. Ich denke, hier soll ich sagen: "Es ist aufregend, in einer so herausfordernden Sprache zu arbeiten!"
Dan Rosenstark

4
Kann ich auch hinzufügen, dass es nicht die Unveränderlichkeit von Strukturen ist, die sie nützlich macht (obwohl es eine sehr gute Sache ist). Sie können Strukturen mutieren, aber Sie müssen die Methoden als markieren, mutatingdamit Sie explizit angeben, welche Funktionen ihren Status ändern. Aber ihre Natur als Werttyp ist das, was wichtig ist. Wenn Sie eine Struktur mit deklarieren let, können Sie keine mutierenden Funktionen darauf aufrufen. Das WWDC 15-Video zur besseren Programmierung durch Werttypen ist hierfür eine hervorragende Ressource.
Abizern

1
Danke @Abizern, ich habe das nie wirklich verstanden, bevor ich deinen Kommentar gelesen habe. Für Objekte ist let vs. var kein großer Unterschied, aber für Strukturen ist es riesig. Vielen Dank für den Hinweis.
Dan Rosenstark

27

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

Wenn Sie nicht wissen, was ein Werttyp und ein Referenztyp sind, lesen Sie Was ist der Unterschied zwischen Referenzübergabe und Wertübergabe?

Basierend auf Mikeashs 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- und y-Paaren können kopiert werden. Sie sollten Werttypen sein. Ein Controller, der eine Festplatte darstellt, kann nicht sinnvoll kopiert werden. Das sollte ein Referenztyp sein.

Einige Typen können kopiert werden, aber es ist möglicherweise nicht immer etwas, das Sie möchten. Dies legt nahe, dass es sich um Referenztypen handeln sollte. Beispielsweise kann eine Schaltfläche auf dem Bildschirm konzeptionell kopiert werden. Die Kopie ist nicht ganz identisch mit dem Original. Ein Klick auf die Kopie aktiviert das Original nicht. Die Kopie befindet sich nicht an derselben Stelle auf dem Bildschirm. Wenn Sie die Schaltfläche weitergeben oder in eine neue Variable einfügen, möchten Sie wahrscheinlich auf die ursprüngliche Schaltfläche verweisen und nur dann eine Kopie erstellen, wenn dies ausdrücklich angefordert wird. Das bedeutet, dass Ihr Schaltflächentyp ein Referenztyp sein sollte.

Ansichts- und Fenstersteuerungen sind ein ähnliches Beispiel. Möglicherweise sind sie kopierbar, aber es ist fast nie das, was Sie tun möchten. Sie sollten Referenztypen sein.

Was ist mit Modelltypen? Möglicherweise haben Sie einen Benutzertyp, der einen Benutzer auf Ihrem System darstellt, oder einen Kriminalitätstyp, der eine von einem Benutzer ausgeführte Aktion darstellt. Diese sind ziemlich kopierbar, daher sollten sie wahrscheinlich Werttypen sein. Sie möchten jedoch wahrscheinlich, dass Aktualisierungen des Verbrechens eines Benutzers, die an einer Stelle in Ihrem Programm vorgenommen wurden, für andere Teile des Programms sichtbar sind. Dies legt nahe, dass Ihre Benutzer von einer Art Benutzersteuerung verwaltet werden sollten, die 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 beispielsweise Arrays und Wörterbücher sowie Zeichenfolgen. Sind sie kopierbar? Offensichtlich. Ist das Kopieren von etwas, das Sie möchten, einfach und häufig? Das ist weniger klar.

Die meisten Sprachen sagen "Nein" dazu und machen ihre Sammlungen zu Referenztypen. Dies gilt für Objective-C und Java sowie Python und JavaScript und fast jede andere Sprache, die mir einfällt. (Eine große Ausnahme ist C ++ mit STL-Sammlungstypen, aber C ++ ist der rasende Wahnsinnige der Sprachwelt, der alles seltsam macht.)

Swift sagte "Ja", was bedeutet, dass Typen wie Array und Dictionary und String eher Strukturen als Klassen sind. Sie werden bei der Zuweisung kopiert und als Parameter übergeben. Dies ist eine völlig vernünftige Wahl, solange die Kopie billig ist, was Swift sehr bemüht ist. ...

Ich persönlich benenne meine Klassen nicht so. Normalerweise nenne ich meinen UserManager anstelle von UserController, aber die Idee ist dieselbe

Verwenden Sie außerdem keine Klasse, wenn Sie jede einzelne Instanz einer Funktion überschreiben müssen, dh sie haben keine gemeinsame Funktionalität.

Also anstatt mehrere Unterklassen einer Klasse zu haben. Verwenden Sie mehrere Strukturen, die einem Protokoll entsprechen.


Ein weiterer vernünftiger Fall für Strukturen ist, wenn Sie ein Delta / Diff Ihres alten und neuen Modells erstellen möchten. Mit Referenztypen können Sie dies nicht sofort tun. Bei Werttypen werden die Mutationen nicht gemeinsam genutzt.


1
Genau die Art von Erklärung, nach der ich gesucht habe. Nizza schreiben :)
androCoder-BD

Sehr hilfreiches Controller-Beispiel
Fragen Sie P

1
@AskP Ich habe Mike selbst eine E-Mail geschickt und diesen zusätzlichen Code bekommen :)
Honey

18

Einige Vorteile:

  • automatisch threadsicher, da es nicht gemeinsam genutzt werden kann
  • verbraucht weniger Speicher, da kein ISO und kein Refcount vorhanden sind (und der Stapel wird im Allgemeinen zugewiesen).
  • Methoden werden immer statisch ausgelöst und können daher inline geschaltet werden (obwohl @final dies für Klassen tun kann).
  • aus dem gleichen Grund wie die Thread-Sicherheit einfacher zu überlegen (keine Notwendigkeit, "defensiv zu kopieren", wie es für NSArray, NSString usw. typisch ist)

Sie sind sich nicht sicher, ob dies außerhalb des Rahmens dieser Antwort liegt, aber können Sie den Punkt "Methoden werden immer statisch versendet" erklären (oder verlinken, denke ich)?
Dan Rosenstark

2
Sicher. Ich kann auch eine Einschränkung hinzufügen. Der Zweck des dynamischen Versands besteht darin, eine Implementierung auszuwählen, wenn Sie nicht genau wissen, welche Sie verwenden sollen. In Swift kann dies entweder an der Vererbung liegen (möglicherweise in einer Unterklasse überschrieben) oder an der generischen Funktion (Sie wissen nicht, wie der generische Parameter aussehen wird). Strukturen können nicht vererbt werden, und durch die Optimierung des gesamten Moduls + die generische Spezialisierung werden die unbekannten Generika größtenteils eliminiert, sodass Methoden einfach direkt aufgerufen werden können, anstatt nachschlagen zu müssen, was aufgerufen werden soll. Nicht spezialisierte Generika versenden jedoch immer noch dynamisch für Strukturen
Catfish_Man

1
Danke, tolle Erklärung. Wir erwarten also mehr Laufzeitgeschwindigkeit oder weniger Mehrdeutigkeit aus IDE-Sicht oder beides?
Dan Rosenstark

1
Meistens die ersteren.
Catfish_Man

Nur ein Hinweis, dass Methoden nicht statisch ausgelöst werden, wenn Sie die Struktur über ein Protokoll referenzieren.
Cristik

12

Struktur ist viel schneller als Klasse. Wenn Sie eine Vererbung benötigen, müssen Sie außerdem Class 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 = "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  
}

Jetzt können Sie eine Instanz von beiden erstellen.

var flightA = Flight()

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

Lassen Sie uns diese Instanz nun an zwei Funktionen übergeben, die die ID, Beschreibung, das Ziel usw. ändern.

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

ebenfalls,

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

damit,

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

Wenn wir nun die ID und Beschreibung des Flugs A ausdrucken, erhalten wir

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

Hier können wir sehen, dass die ID und Beschreibung von FlightA geändert wird, da der an die Änderungsmethode übergebene Parameter tatsächlich auf die Speicheradresse des FlightA-Objekts (Referenztyp) verweist.

Wenn wir nun die ID und Beschreibung der FLightB-Instanz drucken, erhalten wir:

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

Hier können wir sehen, dass die FlightB-Instanz nicht geändert wird, da in der Methode "modifyFlight2" die tatsächliche Instanz von Flight2 übergeben wird und nicht die Referenz (Werttyp).


2
Sie haben nie eine Instanz von FLightB
David Seek

1
Warum redest du dann über FlightB Bruder? Here we can see that the FlightB instance is not changed
David Seek

@ManojKarki, tolle Antwort. Ich wollte nur darauf hinweisen, dass Sie Flug A zweimal deklariert haben, als ich glaube, Sie wollten Flug A und dann Flug B deklarieren.
ScottyBlades

11

Structssind value typeund Classessindreference type

  • Werttypen sind schneller als Referenztypen
  • Instanzen vom Wertetyp sind in einer Umgebung mit mehreren Threads sicher, da mehrere Threads die Instanz mutieren können, ohne sich um die Race-Bedingungen oder Deadlocks kümmern zu müssen
  • Der Werttyp hat im Gegensatz zum Referenztyp keine Referenzen. Daher gibt es keine Speicherlecks.

Verwenden Sie einen valueTyp, wenn:

  • Wenn Kopien einen unabhängigen Status haben sollen, werden die Daten im Code über mehrere Threads hinweg verwendet

Benutze einen referenceTyp, wenn:

  • Sie möchten einen gemeinsamen, veränderlichen Status erstellen.

Weitere Informationen finden Sie auch in der Apple-Dokumentation

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


zusätzliche Information

Schnelle Werttypen werden im Stapel beibehalten. In einem Prozess verfügt jeder Thread über einen eigenen Stapelbereich, sodass kein anderer Thread direkt auf Ihren Wertetyp zugreifen kann. Daher keine Race-Bedingungen, Sperren, Deadlocks oder damit verbundene Komplexität der Thread-Synchronisation.

Werttypen benötigen keine dynamische Speicherzuweisung oder Referenzzählung. Beides sind teure Vorgänge. Gleichzeitig werden Methoden zu Werttypen statisch ausgeliefert. Diese schaffen einen großen Vorteil zugunsten von Werttypen in Bezug auf die Leistung.

Zur Erinnerung hier eine Liste von Swift

Werttypen:

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

Referenztypen:

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

5

Die Beantwortung der Frage aus der Perspektive von Werttypen und Referenztypen in diesem Apple-Blogbeitrag erscheint sehr einfach:

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

  • Der Vergleich von Instanzdaten mit == ist sinnvoll
  • Sie möchten, dass Kopien einen unabhängigen Status haben
  • Die Daten werden im Code über mehrere Threads hinweg verwendet

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

  • Der Vergleich der Instanzidentität mit === ist sinnvoll
  • Sie möchten einen gemeinsamen, veränderlichen Status erstellen

Wie in diesem Artikel erwähnt, verhält sich eine Klasse ohne beschreibbare Eigenschaften identisch mit einer Struktur, mit (ich werde hinzufügen) einer Einschränkung: Strukturen eignen sich am besten für threadsichere Modelle - eine zunehmend bevorstehende Anforderung in der modernen App-Architektur.


3

Bei Klassen erhalten Sie eine Vererbung und werden als Referenz übergeben. Strukturen haben keine Vererbung und werden als Wert übergeben.

Es gibt großartige WWDC-Sitzungen zu Swift, diese spezielle Frage wird in einer von ihnen ausführlich beantwortet. Achten Sie darauf, dass Sie sich diese ansehen, da Sie dadurch viel schneller auf den neuesten Stand gebracht werden als im Sprachhandbuch oder im iBook.


Könnten Sie einige Links von dem, was Sie erwähnt haben, bereitstellen? Da auf WWDC einige zur Auswahl stehen, würde ich gerne den sehen, der über dieses spezielle Thema spricht
MMachinegun

Für mich ist dies ein guter Anfang hier: github.com/raywenderlich/…
MMachinegun

2
Er spricht wahrscheinlich über diese großartige Sitzung: Protokollorientierte Programmierung in Swift. (Links: Video , Transkript )
Zekel

2

Ich würde nicht sagen, dass Strukturen weniger Funktionalität bieten.

Sicher, Selbst ist unveränderlich, außer in einer mutierenden Funktion, aber das war es auch schon.

Vererbung funktioniert gut, solange Sie an der guten alten Idee festhalten, dass jede Klasse entweder abstrakt oder endgültig sein sollte.

Implementieren Sie abstrakte Klassen als Protokolle und endgültige Klassen als Strukturen.

Das Schöne an Strukturen ist, dass Sie Ihre Felder veränderbar machen können, ohne einen gemeinsamen veränderlichen Status zu erstellen, da das Kopieren beim Schreiben dafür sorgt :)

Aus diesem Grund sind die Eigenschaften / Felder im folgenden Beispiel alle veränderbar, was ich in Java- oder C # - oder Swift- Klassen nicht tun würde .

Beispiel für eine Vererbungsstruktur mit etwas schmutziger und unkomplizierter Verwendung unten in der Funktion "Beispiel":

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

2

In Swift wurde ein neues Programmiermuster eingeführt, das als protokollorientierte Programmierung bekannt ist.

Schöpfungsmuster:

In schnellen, ist Struct ein Werttyp , die automatisch geklont werden. Daher erhalten wir das erforderliche Verhalten, um das Prototypmuster kostenlos zu implementieren.

Während Klassen der Referenztyp sind, der während der Zuweisung nicht automatisch geklont wird. Um das Prototypmuster zu implementieren, müssen Klassen das NSCopyingProtokoll übernehmen.


Flache Kopie dupliziert nur die Referenz, die auf diese Objekte verweist, während tiefe Kopie die Referenz des Objekts dupliziert.


Das Implementieren einer tiefen Kopie für jeden Referenztyp ist zu einer mühsamen Aufgabe geworden. Wenn Klassen weitere Referenztypen enthalten, müssen wir für jede der Referenzeigenschaften ein Prototypmuster implementieren. Und dann müssen wir tatsächlich den gesamten Objektgraphen kopieren, indem wir das NSCopyingProtokoll implementieren .

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

class Address{
   var street:String
   ...
} 

Durch die Verwendung von Strukturen und Aufzählungen haben wir unseren Code einfacher gemacht, da wir die Kopierlogik nicht implementieren müssen.


1

Viele Cocoa-APIs erfordern NSObject-Unterklassen, wodurch Sie zur Verwendung von Klassen gezwungen werden. Ansonsten können Sie anhand der folgenden Fälle aus dem Swift-Blog von Apple entscheiden, ob Sie einen Struktur- / Aufzählungswerttyp oder einen Klassenreferenztyp verwenden möchten.

https://developer.apple.com/swift/blog/?id=10


0

Ein Punkt, der in diesen Antworten nicht beachtet wird, ist, dass eine Variable, die eine Klasse gegen eine Struktur enthält, noch eine letWeile Änderungen an den Eigenschaften des Objekts zulässt, während Sie dies mit einer Struktur nicht tun können.

Dies ist nützlich, wenn Sie nicht möchten, dass die Variable jemals auf ein anderes Objekt zeigt, aber dennoch das Objekt ändern müssen, dh wenn Sie viele Instanzvariablen haben, die Sie nacheinander aktualisieren möchten. Wenn es sich um eine Struktur handelt, müssen Sie zulassen, dass die Variable insgesamt auf ein anderes Objekt zurückgesetzt wird var, da ein konstanter Werttyp in Swift eine Nullmutation ordnungsgemäß zulässt, während sich Referenztypen (Klassen) nicht so verhalten.


0

Da struct Werttypen sind und Sie den Speicher sehr einfach erstellen können, der im Stapel gespeichert wird. Struktur kann leicht zugänglich sein und nach dem Umfang der Arbeit kann die Zuordnung vom Stapelspeicher durch Pop von der Oberseite des Stapels leicht aufgehoben werden. Andererseits ist die Klasse ein Referenztyp, der im Heap gespeichert wird, und Änderungen, die in einem Klassenobjekt vorgenommen werden, wirken sich auf andere Objekte aus, da diese eng miteinander verbunden sind und den Referenztyp. Alle Mitglieder einer Struktur sind öffentlich, während alle Mitglieder einer Klasse privat sind .

Die Nachteile von struct sind, dass es nicht vererbt werden kann.


-7
  • Struktur und Klasse sind benutzerdefinierte Datentypen

  • Standardmäßig ist die Struktur öffentlich, während die Klasse privat ist

  • Klasse implementiert das Prinzip der Kapselung

  • Objekte einer Klasse werden im Heapspeicher erstellt

  • Die Klasse wird zur Wiederverwendbarkeit verwendet, während die Struktur zum Gruppieren der Daten in derselben Struktur verwendet wird

  • Strukturdatenelemente können nicht direkt initialisiert werden, sondern können von außerhalb der Struktur zugewiesen werden

  • Klassendatenelemente können direkt vom Konstruktor ohne Parameter initialisiert und vom parametrisierten Konstruktor zugewiesen werden


2
Die schlechteste Antwort aller Zeiten!
J. Doe

Kopieren Einfügen Antwort
jawadAli
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.