Perché scegliere Struct Over Class?


476

Giocando con Swift, proveniente da un background Java, perché dovresti scegliere un Struct anziché un Class? Sembra che siano la stessa cosa, con un Struct che offre meno funzionalità. Perché sceglierlo allora?


11
Le strutture vengono sempre copiate quando vengono passate nel codice e non usano il conteggio dei riferimenti. fonte: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex

4
Direi che le strutture sono più appropriate per contenere i dati, non la logica. Per parlare in termini Java, immagina le strutture come "oggetti valore".
Vincent Guerci,

6
Sono sorpreso in tutta questa conversazione che non vi è alcuna menzione diretta di copy-on-write aka lazy copy . Qualsiasi preoccupazione in merito alle prestazioni della copia strutt è per lo più discutibile a causa di questo disegno.
David James,

3
Scegliere una struttura piuttosto che una classe non è una questione di opinione. Ci sono motivi specifici per scegliere l'uno o l'altro.
David James,

Consiglio vivamente di capire perché Array non è threadSafe . È correlato perché gli array e le strutture sono entrambi tipi di valore. Tutte le risposte qui menzionano che con le strutture / matrici / tipi di valore non avrà mai un problema di sicurezza della discussione, ma c'è un caso d'angolo in cui lo farai.
Miele

Risposte:


548

Secondo il molto popolare talk del WWDC 2015, Protocol Oriented Programming in Swift ( video , trascrizione ), Swift offre una serie di funzionalità che rendono le strutture migliori delle classi in molte circostanze.

Le strutture sono preferibili se sono relativamente piccole e copiabili perché la copia è molto più sicura che avere più riferimenti alla stessa istanza che accade con le classi. Ciò è particolarmente importante quando si passa da una variabile a molte classi e / o in un ambiente multithread. Se puoi sempre inviare una copia della tua variabile in altri luoghi, non devi mai preoccuparti che quell'altro luogo cambi il valore della tua variabile sotto di te.

Con Structs, è molto meno necessario preoccuparsi di perdite di memoria o di più thread in competizione per accedere / modificare una singola istanza di una variabile. (Per i più tecnicamente preparati, l'eccezione è quando si acquisisce una struttura all'interno di una chiusura perché in realtà sta catturando un riferimento all'istanza a meno che non si contrassegni esplicitamente per essere copiato).

Le classi possono anche gonfiarsi perché una classe può ereditare solo da una singola superclasse. Questo ci incoraggia a creare enormi superclassi che racchiudono molte abilità diverse che sono solo vagamente correlate. L'uso dei protocolli, in particolare con le estensioni di protocollo in cui è possibile fornire implementazioni ai protocolli, consente di eliminare la necessità per le classi di ottenere questo tipo di comportamento.

Il discorso illustra questi scenari in cui le classi sono preferite:

  • La copia o il confronto di istanze non ha senso (ad es. Window)
  • La durata dell'istanza è legata ad effetti esterni (ad esempio, File temporaneo)
  • Le istanze sono solo "pozzi" - condotte di sola scrittura verso uno stato esterno (ad es. CGContext)

Implica che le strutture dovrebbero essere predefinite e che le classi dovrebbero essere un fallback.

D'altra parte, la documentazione di The Swift Programming Language è alquanto contraddittoria:

Le istanze della struttura vengono sempre passate per valore e le istanze di classe vengono sempre passate per riferimento. Ciò significa che sono adatti a diversi tipi di attività. Considerando i costrutti e le funzionalità di dati necessari per un progetto, decidere se ciascun costrutto di dati deve essere definito come una classe o una struttura.

Come linea guida generale, considerare la creazione di una struttura quando si applicano una o più di queste condizioni:

  • Lo scopo principale della struttura è incapsulare alcuni valori di dati relativamente semplici.
  • È ragionevole aspettarsi che i valori incapsulati vengano copiati anziché referenziati quando si assegna o si passa attorno a un'istanza di quella struttura.
  • Qualsiasi proprietà memorizzata dalla struttura è a sua volta un tipo di valore, che dovrebbe essere copiato piuttosto che referenziato.
  • Non è necessario che la struttura erediti le proprietà o il comportamento da un altro tipo esistente.

Esempi di buoni candidati per le strutture includono:

  • La dimensione di una forma geometrica, forse incapsulando una proprietà width e una proprietà height, entrambe di tipo Double.
  • Un modo per fare riferimento a intervalli all'interno di una serie, magari incapsulando una proprietà start e una proprietà length, entrambe di tipo Int.
  • Un punto in un sistema di coordinate 3D, forse incapsulando le proprietà x, ye z, ciascuna di tipo Double.

In tutti gli altri casi, definire una classe e creare istanze di quella classe da gestire e passare per riferimento. In pratica, ciò significa che la maggior parte dei costrutti di dati personalizzati dovrebbero essere classi, non strutture.

Qui sta sostenendo che dovremmo usare le classi per impostazione predefinita e usare le strutture solo in circostanze specifiche. Alla fine, è necessario comprendere le implicazioni del mondo reale dei tipi di valore rispetto ai tipi di riferimento e quindi è possibile prendere una decisione informata su quando utilizzare le strutture o le classi. Inoltre, tieni presente che questi concetti sono in continua evoluzione e che la documentazione del linguaggio di programmazione rapida è stata scritta prima che fosse pronunciata la conferenza sulla programmazione orientata al protocollo.


12
@ElgsQianChen il punto centrale di questo articolo è che la struttura dovrebbe essere scelta di default e la classe dovrebbe essere usata solo quando necessario. Le strutture sono molto più sicure e prive di bug, specialmente in un ambiente multithread. Sì, puoi sempre usare una classe al posto di una struttura, ma sono preferibili le strutture.
Drewag,

16
@drewag Questo sembra essere l'esatto contrario di ciò che sta dicendo. Stava dicendo che una classe dovrebbe essere il valore predefinito che usi, non una struttura In practice, this means that most custom data constructs should be classes, not structures.Puoi spiegarmi come, dopo averlo letto, ottieni che la maggior parte dei set di dati dovrebbe essere una struttura e non una classe? Hanno dato un insieme specifico di regole quando qualcosa dovrebbe essere una struttura e praticamente hanno detto "tutti gli altri scenari in cui una classe è migliore".
Matt,

42
L'ultima riga dovrebbe dire "Il mio consiglio personale è l'opposto della documentazione:" ... e quindi è un'ottima risposta!
Dan Rosenstark,

5
Il libro di Swift 2.2 dice ancora l'uso delle classi nella maggior parte delle situazioni.
David James,

6
Struct over Class riduce decisamente la complessità. Ma qual è l'implicazione sull'utilizzo della memoria quando le strutture diventano la scelta predefinita. Quando le cose vengono copiate ovunque invece di riferimento, dovrebbe aumentare l'utilizzo della memoria da parte dell'app. Non dovrebbe?
MadNik,

164

Poiché le istanze struct sono allocate in pila e le istanze di classe sono allocate in heap, le strutture possono talvolta essere drasticamente più veloci.

Tuttavia, dovresti sempre misurarlo da solo e decidere in base al tuo caso d'uso unico.

Si consideri il seguente esempio, che dimostra 2 strategie di wrapping del Inttipo di dati usando structe class. Sto usando 10 valori ripetuti per riflettere meglio il mondo reale, dove hai più campi.

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

Le prestazioni sono misurate usando

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

Il codice è disponibile all'indirizzo https://github.com/knguyen2708/StructVsClassPerformance

AGGIORNAMENTO (27 marzo 2018) :

A partire da Swift 4.0, Xcode 9.2, con Release build su iPhone 6S, iOS 11.2.6, l'impostazione del compilatore Swift è -O -whole-module-optimization:

  • class la versione impiegava 2,06 secondi
  • struct la versione impiegava 4,17e-08 secondi (50.000.000 di volte più veloce)

(Non eseguo più la media di più corse, poiché le varianze sono molto piccole, inferiori al 5%)

Nota : la differenza è molto meno drammatica senza l'ottimizzazione dell'intero modulo. Sarei felice se qualcuno potesse indicare cosa fa realmente la bandiera.


AGGIORNAMENTO (7 maggio 2016) :

A partire da Swift 2.2.1, Xcode 7.3, con Release build su iPhone 6S, iOS 9.3.1, media su 5 run, l'impostazione del compilatore Swift è -O -whole-module-optimization:

  • class la versione ha richiesto 2.159942142s
  • struct versione impiegata 5.83E-08s (37.000.000 volte più veloce)

Nota : come qualcuno ha detto che negli scenari del mondo reale, ci sarà probabilmente più di 1 campo in una struttura, ho aggiunto test per strutture / classi con 10 campi invece di 1. Sorprendentemente, i risultati non variano molto.


RISULTATI ORIGINALI (1 giugno 2014):

(Ha funzionato su struct / class con 1 campo, non 10)

A partire da Swift 1.2, Xcode 6.3.2, con Release build su iPhone 5S, iOS 8.3, media su 5 run

  • class la versione impiegava 9.788332333s
  • struct la versione impiegava 0,010532942s (900 volte più veloce)

VECCHI RISULTATI (da tempi sconosciuti)

(Ha funzionato su struct / class con 1 campo, non 10)

Con il rilascio sviluppato sul mio MacBook Pro:

  • La classversione impiegò 1.10082 sec
  • La structversione ha impiegato 0,02324 sec (50 volte più veloce)

27
È vero, ma sembra che copiare un gruppo di strutture in giro sarebbe più lento della copia di un riferimento a un singolo oggetto. In altre parole, è più veloce copiare un singolo puntatore in giro piuttosto che copiare un blocco di memoria arbitrariamente grande.
Tylerc230,

14
-1 Questo test non è un buon esempio perché esiste solo una var sulla struttura. Notare che se si aggiungono più valori e uno o due oggetti, la versione struct diventa paragonabile alla versione della classe. Più vars aggiungi, più lenta diventa la versione struct.
joshrl,

6
@joshrl ha capito bene, ma un esempio è "buono" o no dipende dalla situazione specifica. Questo codice è stato estratto dalla mia app, quindi è un caso d'uso valido e l'utilizzo delle strutture ha migliorato notevolmente le prestazioni della mia app. Probabilmente non è un caso d'uso comune (beh, il caso d'uso comune è, per la maggior parte delle app, a nessuno importa quanto velocemente possano essere passati i dati, poiché il collo di bottiglia si verifica da qualche altra parte, ad esempio connessioni di rete, comunque, l'ottimizzazione non è che critico quando si hanno dispositivi GHz con GB o RAM).
Khanh Nguyen,

26
Per quanto ho capito, la copia in rapido è ottimizzata per avvenire al momento WRITE. Ciò significa che non è stata creata alcuna copia della memoria fisica a meno che la nuova copia non stia per essere modificata.
Matjan,

6
Questa risposta sta mostrando un esempio estremamente banale, al punto da essere irrealistico e quindi errato per molti casi. Una risposta migliore sarebbe "dipende".
ucciso

60

Somiglianze tra strutture e classi.

Ho creato l'essenza per questo con semplici esempi. https://github.com/objc-swift/swift-classes-vs-structures

E differenze

1. Eredità.

le strutture non possono ereditare rapidamente. Se vuoi

class Vehicle{
}

class Car : Vehicle{
}

Andare per una lezione.

2. Passa vicino

Le strutture veloci passano per valore e le istanze di classe passano per riferimento.

Differenze contestuali

Costante strutturale e variabili

Esempio (usato al WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Definisce una struttura chiamata Punto.

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

Ora se provo a cambiare la x. È un'espressione valida.

point.x = 5

Ma se definissi un punto come costante.

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

In questo caso l'intero punto è costante immutabile.

Se ho usato un punto di classe invece questa è un'espressione valida. Perché in una classe la costante immutabile è il riferimento alla classe stessa non alle sue variabili di istanza (A meno che quelle variabili non siano definite costanti)


Puoi ereditare le strutture in Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy

12
L'essenza di cui sopra riguarda il modo in cui possiamo ottenere sapori ereditari per struct. Vedrai una sintassi simile. A: B. Si tratta di struct chiamato A implementa il protocollo chiamato B. La documentazione di Apple menziona chiaramente che struct non supporta la pura eredità e non lo fa.
MadNik,

2
amico, l'ultimo tuo paragrafo è stato grandioso. Ho sempre saputo che potevi cambiare le costanti ... ma ogni tanto vedevo dove non potevi, quindi ero sconcertato. Questa distinzione lo rese visibile
Miele

28

Ecco alcuni altri motivi da considerare:

  1. le strutture ottengono un inizializzatore automatico che non è necessario mantenere nel codice.

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

Per ottenere questo in una classe, dovresti aggiungere l'inizializzatore e mantenere l'inizializzatore ...

  1. I tipi di raccolta di base come Arraysono strutture. Più li usi nel tuo codice, più ti abituerai a passare per valore anziché a riferimento. Per esempio:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Apparentemente l'immutabilità rispetto alla mutabilità è un argomento enorme, ma molte persone intelligenti pensano che l'immutabilità - le strutture in questo caso - sia preferibile. Oggetti mutabili vs immutabili


4
È vero che ricevi inizializzatori automatici. Si ottiene anche un inizializzatore vuoto quando tutte le proprietà sono opzionali. Ma, se si dispone di una struttura in un framework, è necessario scrivere l'inizializzatore se si desidera che sia disponibile al di fuori internaldell'ambito.
Abizern

2
@Abizern confermato - stackoverflow.com/a/26224873/8047 - e l'uomo che è fastidioso.
Dan Rosenstark,

2
@Abizern ci sono ottime ragioni per tutto in Swift, ma ogni volta che qualcosa è vero in un posto e non in un altro, lo sviluppatore deve sapere più cose. Immagino che qui dovrei dire "è eccitante lavorare in un linguaggio così stimolante!"
Dan Rosenstark,

4
Posso anche aggiungere che non è l'immutabilità delle strutture a renderle utili (anche se è un'ottima cosa). Puoi mutare le strutture, ma devi contrassegnare i metodi in mutatingmodo da essere esplicito su quali funzioni cambiano il loro stato. Ma la loro natura come tipi di valore è ciò che è importante. Se si dichiara uno struct con letnon è possibile chiamare alcuna funzione mutante su di esso. Il video del WWDC 15 su Una migliore programmazione attraverso tipi di valore è una risorsa eccellente su questo.
Abizern,

1
Grazie @Abizern, non l'ho mai capito prima di leggere il tuo commento. Per gli oggetti, let vs. var non fa molta differenza, ma per le strutture è enorme. Grazie per averlo segnalato.
Dan Rosenstark,

27

Supponendo che sappiamo che Struct è un tipo di valore e Class è un tipo di riferimento .

Se non sai quale sia un tipo di valore e un tipo di riferimento, vedi Qual è la differenza tra passaggio per riferimento e passaggio per valore?

Basato sul post di mikeash :

... Vediamo prima alcuni esempi estremi, ovvi. I numeri interi sono ovviamente copiabili. Dovrebbero essere tipi di valore. I socket di rete non possono essere copiati in modo ragionevole. Dovrebbero essere tipi di riferimento. I punti, come nelle coppie x, y, sono copiabili. Dovrebbero essere tipi di valore. Un controller che rappresenta un disco non può essere copiato in modo ragionevole. Questo dovrebbe essere un tipo di riferimento.

Alcuni tipi possono essere copiati ma potrebbe non essere qualcosa che vuoi che accada sempre. Ciò suggerisce che dovrebbero essere tipi di riferimento. Ad esempio, è possibile copiare concettualmente un pulsante sullo schermo. La copia non sarà del tutto identica all'originale. Un clic sulla copia non attiverà l'originale. La copia non occuperà la stessa posizione sullo schermo. Se si passa il pulsante o lo si inserisce in una nuova variabile, probabilmente si farà riferimento al pulsante originale e si vorrebbe fare una copia solo quando viene esplicitamente richiesto. Ciò significa che il tipo di pulsante deve essere un tipo di riferimento.

I controller di vista e finestra sono un esempio simile. Potrebbero essere copiabili, presumibilmente, ma non è quasi mai quello che vorresti fare. Dovrebbero essere tipi di riferimento.

E i tipi di modello? Potresti avere un tipo di utente che rappresenta un utente sul tuo sistema o un tipo di crimine che rappresenta un'azione intrapresa da un utente. Questi sono abbastanza copiabili, quindi dovrebbero probabilmente essere tipi di valore. Tuttavia, probabilmente desideri che gli aggiornamenti al crimine di un utente effettuati in un punto del programma siano visibili ad altre parti del programma. Ciò suggerisce che i tuoi utenti dovrebbero essere gestiti da una sorta di controller utente che sarebbe un tipo di riferimento . per esempio

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

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

Le collezioni sono un caso interessante. Questi includono cose come array e dizionari, nonché stringhe. Sono copiabili? Ovviamente. Copiare è qualcosa che vuoi che accada facilmente e spesso? Questo è meno chiaro.

La maggior parte delle lingue dice "no" a questo e rende i loro tipi di riferimento di raccolte. Questo è vero in Objective-C, Java, Python e JavaScript e in quasi tutte le altre lingue che mi vengono in mente. (Un'eccezione importante è il C ++ con i tipi di raccolta STL, ma C ++ è il folle delirante del mondo linguistico che fa tutto in modo strano.)

Swift ha detto "sì", il che significa che tipi come Array, Dictionary e String sono strutture piuttosto che classi. Vengono copiati al momento dell'assegnazione e al loro passaggio come parametri. Questa è una scelta del tutto ragionevole a condizione che la copia sia economica, cosa che Swift si impegna a fondo. ...

Personalmente non nomina le mie lezioni in quel modo. Di solito il mio UserManager viene chiamato invece UserController ma l'idea è la stessa

Inoltre, non usare la classe quando devi sovrascrivere ogni singola istanza di una funzione, cioè non avere alcuna funzionalità condivisa .

Quindi invece di avere diverse sottoclassi di una classe. Utilizzare diverse strutture conformi a un protocollo.


Un altro caso ragionevole per le strutture è quando vuoi fare un delta / diff del tuo vecchio e nuovo modello. Con i tipi di riferimenti non puoi farlo immediatamente. Con i tipi di valore le mutazioni non sono condivise.


1
Esattamente il tipo di spiegazione che stavo cercando. Nice write up :)
androCoder-BD

Esempio di controller molto utile
chiedi a P

1
@AskP Ho inviato un'email a Mike stesso e ho ricevuto quel pezzo di codice in più :)
Honey

18

Alcuni vantaggi:

  • thread-safe automatico perché non condivisibile
  • utilizza meno memoria a causa di nessuna isa e refcount (e in effetti lo stack è allocato in generale)
  • i metodi vengono sempre inviati staticamente, quindi possono essere inline (anche se @final può farlo per le classi)
  • più facile da ragionare (non è necessario "copiare in modo difensivo" come è tipico di NSArray, NSString, ecc ...) per lo stesso motivo della sicurezza del thread

Non sei sicuro che esuli dallo scopo di questa risposta, ma puoi spiegare (o collegare, suppongo) il punto "i metodi vengono sempre spediti staticamente"?
Dan Rosenstark,

2
Sicuro. Posso anche aggiungere un avvertimento ad esso. Lo scopo dell'invio dinamico è scegliere un'implementazione quando non si conosce in anticipo quale utilizzare. In Swift, ciò può essere dovuto all'ereditarietà (potrebbe essere sovrascritto in una sottoclasse) o alla funzione generica (non si conosce quale sarà il parametro generico). Le strutture non possono essere ereditate e l'ottimizzazione dell'intero modulo + la specializzazione generica elimina principalmente i generici sconosciuti, quindi i metodi possono essere semplicemente chiamati direttamente invece di cercare come chiamare. I generici non specializzati effettuano comunque spedizioni dinamiche per le strutture
Catfish_Man

1
Grazie, ottima spiegazione. Quindi ci aspettiamo una maggiore velocità di esecuzione o meno ambiguità dal punto di vista IDE o entrambi?
Dan Rosenstark,

1
Principalmente il primo.
Catfish_Man

Solo una nota che i metodi non vengono inviati staticamente se si fa riferimento alla struttura tramite un protocollo.
Cristik,

12

La struttura è molto più veloce di Class. Inoltre, se hai bisogno di ereditarietà, devi usare Class. Il punto più importante è che la classe è un tipo di riferimento mentre la struttura è un tipo di valore. per esempio,

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  
}

ora consente di creare l'istanza di entrambi.

var flightA = Flight()

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

ora passiamo queste istanze a due funzioni che modificano l'id, la descrizione, la destinazione ecc.

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

anche,

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

così,

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

ora se stampiamo l'ID e la descrizione del volo A, otteniamo

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

Qui, possiamo vedere l'id e la descrizione di FlightA è cambiata perché il parametro passato al metodo di modifica in realtà punta all'indirizzo di memoria dell'oggetto flightA (tipo di riferimento).

ora se stampiamo l'id e la descrizione dell'istanza di FLightB che otteniamo,

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

Qui possiamo vedere che l'istanza FlightB non è cambiata perché nel metodo modiflight2, l'istanza effettiva di Flight2 è passata anziché riferimento (tipo di valore).


2
non hai mai creato un'istanza di FLightB
David Seek,

1
allora perché stai parlando di FlightB fratello? Here we can see that the FlightB instance is not changed
David Seek,

@ManojKarki, ottima risposta. Volevo solo sottolineare che hai dichiarato il volo A due volte quando penso che tu volessi dichiarare FlightA, quindi FlightB.
ScottyBlades,

11

Structssono value typee Classessonoreference type

  • I tipi di valore sono più veloci dei tipi di riferimento
  • Le istanze del tipo di valore sono sicure in un ambiente multi-thread in quanto più thread possono mutare l'istanza senza doversi preoccupare delle condizioni di competizione o dei deadlock
  • Il tipo di valore non ha riferimenti a differenza del tipo di riferimento; pertanto non vi sono perdite di memoria.

Utilizzare un valuetipo quando:

  • Volete che le copie abbiano uno stato indipendente, i dati verranno usati nel codice su più thread

Utilizzare un referencetipo quando:

  • Volete creare uno stato condiviso e mutabile.

Ulteriori informazioni sono disponibili anche nella documentazione di Apple

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


Informazioni aggiuntive

I tipi di valore rapidi vengono mantenuti nello stack. In un processo, ogni thread ha il proprio spazio di stack, quindi nessun altro thread sarà in grado di accedere direttamente al tipo di valore. Quindi nessuna condizione di competizione, blocco, deadlock o qualsiasi complessità di sincronizzazione del thread correlata.

I tipi di valore non richiedono allocazione dinamica della memoria o conteggio dei riferimenti, entrambi operazioni costose. Allo stesso tempo, i metodi sui tipi di valore vengono inviati staticamente. Questi creano un enorme vantaggio a favore di tipi di valore in termini di prestazioni.

Come promemoria ecco un elenco di Swift

Tipi di valore:

  • struct
  • enum
  • tuple
  • Primitivi (Int, Double, Bool ecc.)
  • Collezioni (Array, String, Dictionary, Set)

Tipi di riferimento:

  • Classe
  • Qualunque cosa provenga da NSObject
  • Funzione
  • Chiusura

5

Rispondere alla domanda dal punto di vista dei tipi di valore rispetto ai tipi di riferimento, da questo post sul blog di Apple sembrerebbe molto semplice:

Utilizzare un tipo di valore [es. Struct, enum] quando:

  • Il confronto dei dati dell'istanza con == ha senso
  • Volete che le copie abbiano uno stato indipendente
  • I dati verranno utilizzati nel codice su più thread

Utilizzare un tipo di riferimento [ad es. Classe] quando:

  • Confrontare l'identità dell'istanza con === ha senso
  • Volete creare uno stato condiviso e mutabile

Come menzionato in quell'articolo, una classe senza proprietà scrivibili si comporterà in modo identico a una struttura, con (aggiungerò) un avvertimento: le strutture sono le migliori per i modelli thread-safe - un requisito sempre più imminente nell'architettura delle app moderne.


3

Con le classi si ottiene l'ereditarietà e si passa per riferimento, le strutture non hanno ereditarietà e vengono passate per valore.

Ci sono ottime sessioni WWDC su Swift, a questa domanda specifica viene data risposta in dettaglio in una di esse. Assicurati di guardarli, perché ti consentiranno di accelerare molto più rapidamente rispetto alla Guida linguistica o all'iBook.


Potresti fornire alcuni link da ciò che hai menzionato? Perché sul WWDC ce ne sono molti tra cui scegliere, mi piacerebbe vedere quello che parla di questo argomento specifico
MMachinegun

Per me questo è un buon inizio qui: github.com/raywenderlich/…
MMachinegun

2
Probabilmente sta parlando di questa grande sessione: Programmazione orientata al protocollo in Swift. (Link: video , trascrizione )
zekel

2

Non direi che le strutture offrano meno funzionalità.

Certo, il sé è immutabile se non in una funzione mutante, ma questo è tutto.

L'ereditarietà funziona bene fintanto che rimani fedele alla buona vecchia idea che ogni classe dovrebbe essere astratta o finale.

Implementare le classi astratte come protocolli e le classi finali come strutture.

La cosa bella delle strutture è che puoi rendere i tuoi campi mutabili senza creare uno stato mutabile condiviso perché la copia su scrittura se ne occupa :)

Ecco perché le proprietà / i campi nel seguente esempio sono tutti mutabili, cosa che non farei in Java o C # o in classi veloci .

Esempio di struttura ereditaria con un po 'di utilizzo sporco e semplice nella parte inferiore nella funzione denominata "esempio":

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 è stato introdotto un nuovo modello di programmazione noto come Programmazione orientata al protocollo.

Motivo creazionale:

In breve, Struct è un tipo di valore che viene clonato automaticamente. Pertanto otteniamo il comportamento richiesto per implementare gratuitamente il modello prototipo.

Considerando che le classi sono il tipo di riferimento, che non viene automaticamente clonato durante l'assegnazione. Per implementare il modello prototipo, le classi devono adottare il NSCopyingprotocollo.


La copia superficiale duplica solo il riferimento, che punta a quegli oggetti mentre la copia profonda duplica il riferimento dell'oggetto.


L'implementazione della copia profonda per ciascun tipo di riferimento è diventata un'attività noiosa. Se le classi includono un ulteriore tipo di riferimento, dobbiamo implementare un modello prototipo per ciascuna delle proprietà dei riferimenti. E quindi dobbiamo effettivamente copiare l'intero grafico dell'oggetto implementando il NSCopyingprotocollo.

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

class Address{
   var street:String
   ...
} 

Usando le strutture e gli enum , abbiamo semplificato il nostro codice poiché non dobbiamo implementare la logica di copia.


1

Molte API Cocoa richiedono sottoclassi NSObject, che ti costringono a utilizzare la classe. Oltre a ciò, puoi utilizzare i seguenti casi dal blog Swift di Apple per decidere se utilizzare un tipo di valore struct / enum o un tipo di riferimento di classe.

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


0

Un punto che non attira l'attenzione su queste risposte è che una variabile che detiene una classe rispetto a una struttura può essere un letpo 'che consente comunque di modificare le proprietà dell'oggetto, mentre non è possibile farlo con una struttura.

Ciò è utile se non si desidera che la variabile punti mai verso un altro oggetto, ma è comunque necessario modificarlo, ovvero nel caso in cui si disponga di molte variabili di istanza che si desidera aggiornare una dopo l'altra. Se si tratta di una struttura, è necessario consentire la reimpostazione totale della variabile su un altro oggetto varper poterlo fare, poiché un tipo di valore costante in Swift consente correttamente la mutazione zero, mentre i tipi di riferimento (classi) non si comportano in questo modo.


0

Dato che struct sono tipi di valore e puoi creare molto facilmente la memoria che viene archiviata nello stack. La struttura può essere facilmente accessibile e dopo l'ambito del lavoro viene facilmente allocata dalla memoria dello stack attraverso il pop dalla parte superiore dello stack. D'altra parte la classe è un tipo di riferimento che memorizza nell'heap e le modifiche apportate in un oggetto di classe avranno un impatto su un altro oggetto poiché sono strettamente accoppiate e di tipo di riferimento. Tutti i membri di una struttura sono pubblici mentre tutti i membri di una classe sono privati .

Lo svantaggio di struct è che non può essere ereditato.


-7
  • La struttura e la classe sono tipi di dati sfidati dall'utente

  • Per impostazione predefinita, la struttura è pubblica mentre la classe è privata

  • La classe implementa il principio dell'incapsulamento

  • Gli oggetti di una classe vengono creati nella memoria dell'heap

  • La classe viene utilizzata per la riutilizzabilità, mentre la struttura viene utilizzata per raggruppare i dati nella stessa struttura

  • I membri dei dati della struttura non possono essere inizializzati direttamente ma possono essere assegnati dall'esterno della struttura

  • I membri dei dati di classe possono essere inizializzati direttamente dal parametro less constructor e assegnati dal costruttore con parametri


2
La peggior risposta di sempre!
J. Doe,

copia incolla risposta
jawadli
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.