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?
Antworten
Zu viele Anzeigen?Einige Vorteile:
- automatisch threadsicher aufgrund der Nichtteilbarkeit
- benötigt weniger Speicher aufgrund des Fehlens von isa und refcount (wird in der Regel tatsächlich auf dem Stapel allokiert)
- Methoden werden immer statisch verknüpft, können also inlined werden (obwohl @final dies auch für Klassen tun kann)
- einfacher zu verstehen (kein Bedarf an „defensivem Kopieren“, wie es typisch bei NSArray, NSString, etc ... ist) aus dem gleichen Grund wie die Threadsicherheit
Structs
sind Werttypen
und Klassen
sind Verweistypen
- Werttypen sind schneller als Verweistypen
- Instanzen von Werttypen sind sicher in einer mehrfädigen Umgebung, da mehrere Threads die Instanz mutieren können, ohne sich um Rennbedingungen oder Deadlocks kümmern zu müssen
- Werttypen haben im Gegensatz zu Verweistypen keine Verweise; daher gibt es keine Speicherlecks.
Verwenden Sie einen Wert
Typ, wenn:
- Sie möchten, dass Kopien einen unabhängigen Zustand haben, die Daten in Code über mehrere Threads verwendet werden
Verwenden Sie einen Verweis
Typ, wenn:
- Sie gemeinsamen, veränderbaren Zustand erstellen möchten.
Weitere Informationen finden Sie auch in der Apple-Dokumentation
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Zusätzliche Informationen
Swift-Wertetypen werden im Stapel gehalten. In einem Prozess hat jeder Thread seinen eigenen Stapelplatz, sodass kein anderer Thread direkt auf Ihren Wertetyp zugreifen kann. Daher keine Rennbedingungen, Sperren, Deadlocks oder andere damit verbundene Thread-Synchronisierungskomplexitäten.
Wertetypen benötigen keine dynamische Speicherzuweisung oder Referenzzählung, beides teure Operationen. Gleichzeitig werden Methoden auf Wertetypen statisch ausgeführt. Diese bieten einen erheblichen Vorteil für Wertetypen in Bezug auf die Leistung.
Als Erinnerung hier ist eine Liste von Swift
Wertetypen:
- Struct
- Enum
- Tupel
- Primitiven (Int, Double, Bool usw.)
- Sammlungen (Array, String, Dictionary, Set)
Verweistypen:
- Klasse
- Alles, was von NSObject kommt
- Funktion
- Abbildung
Struktur ist viel schneller als Klasse. Außerdem, wenn Sie Vererbung benötigen, müssen Sie Klasse verwenden. Der wichtigste Punkt ist, dass Klasse ein Referenztyp ist, während Struktur ein Werttyp ist. zum Beispiel,
class Flight {
var id:Int?
var description:String?
var destination:String?
var airlines:String?
init(){
id = 100
description = "erster Flug von Virgin Airlines"
destination = "London"
airlines = "Virgin Airlines"
}
}
struct Flight2 {
var id:Int
var description:String
var destination:String
var airlines:String
}
Jetzt erstellen wir Instanzen von beiden.
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"erster Flug von Virgin Airlines", destination:"London" , airlines:"Virgin Airlines" )
Jetzt übergeben wir diese Instanzen an zwei Funktionen, die die id, Beschreibung, Ziel usw. ändern.
func modifyFlight(flight:Flight) -> Void {
flight.id = 200
flight.description = "zweiter Flug von Virgin Airlines"
flight.destination = "New York"
flight.airlines = "Virgin Airlines"
}
auch,
func modifyFlight2(flight2: Flight2) -> Void {
var passedFlight = flight2
passedFlight.id = 200
passedFlight.description = "zweiter Flug von Virgin Airlines"
}
Also,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
Jetzt, wenn wir die id und Beschreibung von FlightA drucken, erhalten wir
id = 200
Beschreibung = "zweiter Flug von Virgin Airlines"
Hier können wir sehen, dass sich die id und Beschreibung von FlightA geändert haben, weil der an die Änderungsmethode übergebene Parameter tatsächlich auf die Speicheradresse des FlugA-Objekts (Referenztyp) zeigt.
Jetzt, wenn wir die id und Beschreibung der Instanz FLightB drucken, erhalten wir,
id = 100
Beschreibung = "erster Flug von Virgin Airlines"
Hier können wir sehen, dass sich die Instanz FlightB nicht geändert hat, weil in der modifyFlight2-Methode tatsächlich die Instanz von Flight2 übergeben wird, anstatt der Referenz (Werttyp).
Beim Beantworten der Frage aus der Perspektive von Werttypen vs. Referenztypen erscheint es gemäß diesem Apple-Blog-Beitrag sehr einfach:
Verwenden Sie einen Werttyp [z.B. struct, enum], wenn:
- das Vergleichen von Instanzdaten mit == sinnvoll ist
- Sie möchten, dass Kopien einen unabhängigen Zustand haben
- die Daten in Code über mehrere Threads hinweg verwendet werden
Verwenden Sie einen Referenztyp [z.B. class], wenn:
- das Vergleichen der Instanzzugehörigkeit mit === sinnvoll ist
- Sie gemeinsamen, veränderbaren Zustand erstellen möchten
Wie in dem Artikel erwähnt, wird sich eine Klasse ohne schreibbare Eigenschaften identisch wie ein struct verhalten, mit (ich werde hinzufügen) einem Vorbehalt: Structs sind am besten für thread-sichere Modelle geeignet - eine zunehmend dringliche Anforderung in der modernen App-Architektur.
Struct vs Class
[Stack vs Heap]
[Value vs Reference type]
Struct
ist eher bevorzugt. Aber Struct
löst nicht alle Probleme von selbst. Normalerweise hört man, dass der Werttyp
auf dem Stapel allokiert wird, aber das stimmt nicht immer. Nur lokale Variablen werden auf dem Stapel allokiert
//simple blocks
struct WertTyp {}
class ReferenzTyp {}
struct StructMitRef {
let ref1 = ReferenzTyp()
}
class KlasseMitRef {
let ref1 = ReferenzTyp()
}
func foo() {
//simple blocks
let wertTyp1 = WertTyp()
let refTyp1 = ReferenzTyp()
//RetainCount
//StructMitRef
let structMitRef1 = StructMitRef()
let structMitRef1Kopie = structMitRef1
print("Original:", CFGetRetainCount(structMitRef1 as CFTypeRef)) //1
print("Ref1:", CFGetRetainCount(structMitRef1.ref1)) //2 (ursprünglich 3)
//KlasseMitRef
let klasseMitRef1 = KlasseMitRef()
let klasseMitRef1Kopie = klasseMitRef1
print("Original:", CFGetRetainCount(klasseMitRef1)) //2 (ursprünglich 3)
print("Ref1:", CFGetRetainCount(klasseMitRef1.ref1)) //1 (ursprünglich 2)
}
Sie sollten nicht auf retainCount
verwenden/verlassen, da es keine nützlichen Informationen liefert
Um Stapel oder Heap zu überprüfen
Während der Kompilierung kann Swift Intermediate Language(SIL)
Ihren Code optimieren
swiftc -emit-silgen - .swift
//z.B.
swiftc -emit-silgen -Onone file.swift
//emit-silgen -> emit-sil(ist in jedem Fall verwendet)
//-emit-silgen Rohes SIL-Datei(en) ausgeben
//-emit-sil Kanonische SIL-Datei(en) ausgeben
//Optimierung: O, Osize, Onone. Entspricht dem Swift-Compiler - Codegenerierung -> Optimierungsgrad
Dort finden Sie alloc_stack
(Speicherung auf Stapel) und alloc_box
(Speicherung auf Heap)