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?
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?
Antworten:
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.
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".
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 Int
Datentyps mit struct
und 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 Sekundenstruct
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.159942142sstruct
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.788332333sstruct
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:
class
Version dauerte 1.10082 Sekundenstruct
Version dauerte 0,02324 Sekunden (50-mal schneller)Ich habe das Wesentliche dafür mit einfachen Beispielen erstellt. https://github.com/objc-swift/swift-classes-vs-structures
Strukturen können nicht schnell erben. Falls Sie es wollen
class Vehicle{
}
class Car : Vehicle{
}
Geh in eine Klasse.
Schnelle Strukturen werden als Wert und Klasseninstanzen als Referenz übergeben.
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)
Hier sind einige andere Gründe zu berücksichtigen:
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 ...
Grundlegende Sammlungstypen wie Array
sind 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]
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
internal
Gültigkeitsbereichs verfügbar ist .
mutating
damit 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.
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.
Einige Vorteile:
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).
Here we can see that the FlightB instance is not changed
Structs
sind value type
und Classes
sindreference type
Verwenden Sie einen value
Typ, wenn:
Benutze einen reference
Typ, wenn:
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:
Referenztypen:
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.
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.
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)")
}
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 NSCopying
Protokoll ü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 NSCopying
Protokoll 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.
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.
Ein Punkt, der in diesen Antworten nicht beachtet wird, ist, dass eine Variable, die eine Klasse gegen eine Struktur enthält, noch eine let
Weile Ä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.
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.
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