Pourquoi choisir Struct Over Class?

476

Jouer avec Swift, venant d'un arrière-plan Java, pourquoi voudriez-vous choisir un Struct au lieu d'une classe? On dirait que c'est la même chose, avec un Struct offrant moins de fonctionnalités. Pourquoi le choisir alors?

bluedevil2k
la source
11
Les structures sont toujours copiées lorsqu'elles sont transmises dans votre code et n'utilisent pas le comptage de références. source: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex
4
Je dirais que les structures sont plus appropriées pour contenir des données, pas de la logique. Pour parler en termes Java, imaginez les structures comme des "objets de valeur".
Vincent Guerci
6
Je suis étonné de voir dans cette conversation il n'y a aucune mention directe de copie sur écriture aka copie paresseuse . Toutes les préoccupations concernant les performances de la copie de structure sont principalement sans objet en raison de cette conception.
David James
3
Choisir une structure plutôt qu'une classe n'est pas une question d'opinion. Il y a des raisons spécifiques de choisir l'un ou l'autre.
David James
Je recommande fortement de voir pourquoi Array n'est pas threadSafe . C'est lié car les tableaux et les structures sont tous les deux des types de valeur. Toutes les réponses ici mentionnent qu'avec les types structs / arrays / value, il n'y aura jamais de problème de sécurité des threads, mais il y a un cas d'angle dans lequel vous le ferez.
Honey

Réponses:

548

Selon la très populaire conférence WWDC 2015 sur la programmation orientée protocole dans Swift ( vidéo , transcription ), Swift fournit un certain nombre de fonctionnalités qui rendent les structures meilleures que les classes dans de nombreuses circonstances.

Les structures sont préférables si elles sont relativement petites et copiables car la copie est bien plus sûre que d'avoir plusieurs références à la même instance comme cela se produit avec les classes. Ceci est particulièrement important lors du passage d'une variable à de nombreuses classes et / ou dans un environnement multithread. Si vous pouvez toujours envoyer une copie de votre variable à d'autres endroits, vous n'avez jamais à vous soucier que cet autre endroit change la valeur de votre variable sous vous.

Avec Structs, il est beaucoup moins nécessaire de s'inquiéter des fuites de mémoire ou des courses de plusieurs threads pour accéder / modifier une seule instance d'une variable. (Pour les plus techniques, l'exception à cela est lors de la capture d'une structure à l'intérieur d'une fermeture, car elle capture en fait une référence à l'instance, sauf si vous la marquez explicitement pour la copie).

Les classes peuvent également devenir gonflées car une classe ne peut hériter que d'une seule superclasse. Cela nous encourage à créer d'énormes superclasses qui englobent de nombreuses capacités différentes qui ne sont que vaguement liées. L'utilisation de protocoles, en particulier avec des extensions de protocole où vous pouvez fournir des implémentations aux protocoles, vous permet d'éliminer le besoin de classes pour obtenir ce type de comportement.

L'exposé présente ces scénarios où les classes sont préférées:

  • Copier ou comparer des instances n'a pas de sens (par exemple, Windows)
  • La durée de vie de l'instance est liée aux effets externes (par exemple, TemporaryFile)
  • Les instances ne sont que des "puits" - conduits en écriture seule vers l'état externe (par exempleCGContext)

Cela implique que les structures devraient être la valeur par défaut et que les classes devraient être une solution de rechange.

D'un autre côté, la documentation du langage de programmation Swift est quelque peu contradictoire:

Les instances de structure sont toujours transmises par valeur et les instances de classe sont toujours transmises par référence. Cela signifie qu'ils sont adaptés à différents types de tâches. Lorsque vous considérez les constructions de données et les fonctionnalités dont vous avez besoin pour un projet, décidez si chaque construction de données doit être définie en tant que classe ou structure.

En règle générale, envisagez de créer une structure lorsqu'une ou plusieurs de ces conditions s'appliquent:

  • L'objectif principal de la structure est d'encapsuler quelques valeurs de données relativement simples.
  • Il est raisonnable de s'attendre à ce que les valeurs encapsulées soient copiées plutôt que référencées lorsque vous affectez ou contournez une instance de cette structure.
  • Toutes les propriétés stockées par la structure sont elles-mêmes des types de valeurs, qui devraient également être copiées plutôt que référencées.
  • La structure n'a pas besoin d'hériter des propriétés ou du comportement d'un autre type existant.

Voici quelques exemples de bons candidats aux structures:

  • Taille d'une forme géométrique, encapsulant peut-être une propriété width et une propriété height, toutes deux de type Double.
  • Une façon de faire référence aux plages d'une série, peut-être en encapsulant une propriété de début et une propriété de longueur, toutes deux de type Int.
  • Un point dans un système de coordonnées 3D, encapsulant peut-être des propriétés x, y et z, chacune de type Double.

Dans tous les autres cas, définissez une classe et créez des instances de cette classe à gérer et à transmettre par référence. En pratique, cela signifie que la plupart des constructions de données personnalisées doivent être des classes et non des structures.

Ici, il prétend que nous devrions utiliser par défaut les classes et utiliser les structures uniquement dans des circonstances spécifiques. En fin de compte, vous devez comprendre l'implication réelle des types de valeur par rapport aux types de référence, puis vous pouvez prendre une décision éclairée sur le moment d'utiliser des structures ou des classes. Gardez également à l'esprit que ces concepts sont en constante évolution et que la documentation du langage de programmation rapide a été écrite avant la présentation de la programmation orientée protocole.

drewag
la source
12
@ElgsQianChen tout l'intérêt de cet article est que la structure doit être choisie par défaut et que la classe ne doit être utilisée que lorsque cela est nécessaire. Les structures sont beaucoup plus sûres et sans bogues, en particulier dans un environnement multithread. Oui, vous pouvez toujours utiliser une classe à la place d'une structure, mais les structures sont préférables.
drewag
16
@drewag Cela semble être l'exact opposé de ce qu'il dit. Il disait qu'une classe devrait être la valeur par défaut que vous utilisez, pas une structure In practice, this means that most custom data constructs should be classes, not structures.Pouvez-vous m'expliquer comment, après avoir lu cela, vous obtenez que la plupart des ensembles de données devraient être des structures et non des classes? Ils ont donné un ensemble spécifique de règles quand quelque chose devrait être une structure et ont dit à peu près "tous les autres scénarios, une classe est meilleure".
Matt
42
La dernière ligne devrait dire: "Mon conseil personnel est l'opposé de la documentation:" ... et alors c'est une excellente réponse!
Dan Rosenstark
5
Le livre Swift 2.2 dit toujours utiliser des classes dans la plupart des situations.
David James
6
Struct sur Class est certainement réduire la complexité. Mais quelle est l'implication sur l'utilisation de la mémoire lorsque les structures deviennent le choix par défaut. Lorsque les choses sont copiées partout au lieu de référence, cela devrait augmenter l'utilisation de la mémoire par l'application. N'est-ce pas?
MadNik
164

Étant donné que les instances de structure sont allouées sur la pile et les instances de classe sont allouées sur le tas, les structures peuvent parfois être considérablement plus rapides.

Cependant, vous devez toujours le mesurer vous-même et décider en fonction de votre cas d'utilisation unique.

Prenons l'exemple suivant, qui illustre 2 stratégies d'encapsulation Intde type de données à l'aide de structet class. J'utilise 10 valeurs répétées pour mieux refléter le monde réel, où vous avez plusieurs champs.

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

La performance est mesurée en utilisant

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

Le code peut être trouvé à https://github.com/knguyen2708/StructVsClassPerformance

MISE À JOUR (27 mars 2018) :

Depuis Swift 4.0, Xcode 9.2, exécutant la version Release sur iPhone 6S, iOS 11.2.6, le paramètre du compilateur Swift est le suivant -O -whole-module-optimization:

  • class la version a pris 2,06 secondes
  • struct la version a pris 4,17e-08 secondes (50 000 000 fois plus rapide)

(Je ne fais plus la moyenne de plusieurs séries, car les écarts sont très faibles, moins de 5%)

Remarque : la différence est beaucoup moins dramatique sans optimisation du module entier. Je serais heureux si quelqu'un pouvait indiquer ce que fait réellement le drapeau.


MISE À JOUR (7 mai 2016) :

Depuis Swift 2.2.1, Xcode 7.3, exécutant la version Release sur iPhone 6S, iOS 9.3.1, en moyenne sur 5 exécutions, le paramètre du compilateur Swift est le suivant -O -whole-module-optimization:

  • class la version a pris 2.159942142s
  • struct la version a pris 5,83E-08s (37 000 000 fois plus rapide)

Remarque : comme quelqu'un a mentionné que dans les scénarios du monde réel, il y aurait probablement plus d'un champ dans une structure, j'ai ajouté des tests pour les structures / classes avec 10 champs au lieu de 1. Étonnamment, les résultats ne varient pas beaucoup.


RÉSULTATS ORIGINAUX (1er juin 2014):

(Exécuté sur struct / classe avec 1 champ, pas 10)

Depuis Swift 1.2, Xcode 6.3.2, exécutant la version Release sur iPhone 5S, iOS 8.3, en moyenne sur 5 exécutions

  • class la version a pris 9.788332333s
  • struct la version a pris 0.010532942s (900 fois plus rapide)

ANCIENS RÉSULTATS (d'une heure inconnue)

(Exécuté sur struct / classe avec 1 champ, pas 10)

Avec la version construite sur mon MacBook Pro:

  • La classversion a pris 1.10082 sec
  • La structversion a pris 0.02324 sec (50 fois plus rapide)
Khanh Nguyen
la source
27
C'est vrai, mais il semble que copier un tas de structures autour serait plus lent que copier une référence à un seul objet. En d'autres termes, il est plus rapide de copier un seul pointeur que de copier un bloc de mémoire arbitrairement grand.
Tylerc230
14
-1 Ce test n'est pas un bon exemple car il n'y a qu'une seule var sur la structure. Notez que si vous ajoutez plusieurs valeurs et un ou deux objets, la version de structure devient comparable à la version de classe. Plus vous ajoutez de vars, plus la version structurée est lente.
joshrl
6
@joshrl a compris votre point de vue, mais un exemple est "bon" ou non dépend de la situation spécifique. Ce code a été extrait de ma propre application, c'est donc un cas d'utilisation valide, et l'utilisation de structures a considérablement amélioré les performances de mon application. Ce n'est probablement pas un cas d'utilisation commun (eh bien, le cas d'utilisation commun est que, pour la plupart des applications, personne ne se soucie de la vitesse à laquelle les données peuvent être transmises, car le goulot d'étranglement se produit ailleurs, par exemple, les connexions réseau, de toute façon, l'optimisation n'est pas que critique lorsque vous avez des périphériques GHz avec Go ou RAM).
Khanh Nguyen
26
Autant que je sache, la copie en swift est optimisée pour se produire au moment de l'écriture. Ce qui signifie qu'aucune copie de mémoire physique n'est effectuée à moins que la nouvelle copie ne soit sur le point d'être modifiée.
Matjan
6
Cette réponse montre un exemple extrêmement trivial, au point d'être irréaliste et donc incorrect dans de nombreux cas. Une meilleure réponse serait «ça dépend».
iwasrobbed
60

Similitudes entre les structures et les classes.

J'ai créé l'essentiel pour cela avec des exemples simples. https://github.com/objc-swift/swift-classes-vs-structures

Et les différences

1. Héritage.

les structures ne peuvent pas hériter rapidement. Si tu veux

class Vehicle{
}

class Car : Vehicle{
}

Allez en classe.

2. Passer

Les structures Swift passent par valeur et les instances de classe passent par référence.

Différences contextuelles

Constante de structure et variables

Exemple (utilisé lors de la WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Définit une structure appelée Point.

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

Maintenant, si j'essaie de changer le x. C'est une expression valable.

point.x = 5

Mais si je définissais un point comme constant.

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

Dans ce cas, le point entier est immuable constant.

Si j'ai utilisé une classe Point à la place, c'est une expression valide. Parce que dans une classe, la constante immuable est la référence à la classe elle-même et non à ses variables d'instance (sauf si ces variables sont définies comme constantes)

MadNik
la source
Vous pouvez hériter de structures dans Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy
12
L'essentiel ci-dessus concerne la façon dont nous pouvons obtenir des saveurs d'héritage pour struct. Vous verrez une syntaxe comme. A: B. C'est un struct appelé A implémente un protocole appelé B. La documentation Apple mentionne clairement que struct ne prend pas en charge l'héritage pur et ce n'est pas le cas.
MadNik
2
l'homme ce dernier paragraphe était génial. J'ai toujours su que tu pouvais changer les constantes ... mais de temps en temps, je voyais où tu ne pouvais pas, alors j'étais déconcerté. Cette distinction l'a rendu visible
Honey
28

Voici quelques autres raisons à considérer:

  1. Les structures obtiennent un initialiseur automatique que vous n'avez pas du tout besoin de conserver dans le code.

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

Pour obtenir ceci dans une classe, vous devez ajouter l'initialiseur et maintenir l'intialiseur ...

  1. Les types de collection de base comme Arrayles structures sont. Plus vous les utilisez dans votre propre code, plus vous vous habituerez à passer par valeur plutôt que par référence. Par exemple:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Apparemment, l'immuabilité contre la mutabilité est un sujet énorme, mais beaucoup de gens intelligents pensent que l'immuabilité - les structures dans ce cas - est préférable. Objets mutables vs objets immuables

Dan Rosenstark
la source
4
Il est vrai que vous obtenez des initialiseurs automatiques. Vous obtenez également un initialiseur vide lorsque toutes les propriétés sont facultatives. Mais, si vous avez une structure dans un Framework, vous devez réellement écrire l'initialiseur vous-même si vous voulez qu'il soit disponible en dehors de la internalportée.
Abizern
2
@Abizern a confirmé - stackoverflow.com/a/26224873/8047 - et l'homme qui est ennuyeux.
Dan Rosenstark
2
@Abizern il y a de bonnes raisons pour tout dans Swift, mais chaque fois que quelque chose se passe dans un endroit et pas dans un autre, le développeur doit en savoir plus. Je suppose que c'est là que je suis censé dire: "c'est excitant de travailler dans un langage aussi difficile!"
Dan Rosenstark
4
Puis-je également ajouter que ce n'est pas l'immuabilité des structures qui les rend utiles (bien que ce soit une très bonne chose). Vous pouvez muter les structures, mais vous devez marquer les méthodes comme mutatingsi vous êtes explicite sur les fonctions qui changent leur état. Mais leur nature en tant que types de valeur est ce qui est important. Si vous déclarez une structure avec letvous ne pouvez pas appeler de fonctions de mutation dessus. La vidéo de la WWDC 15 sur Une meilleure programmation grâce aux types de valeur est une excellente ressource à ce sujet.
Abizern
1
Merci @Abizern, je n'ai jamais vraiment compris cela avant de lire votre commentaire. Pour les objets, let vs var n'est pas très différent, mais pour les structures c'est énorme. Merci de l'avoir signalé.
Dan Rosenstark
27

En supposant que nous savons que Struct est un type de valeur et Class est un type de référence .

Si vous ne savez pas ce qu'est un type de valeur et un type de référence, voir Quelle est la différence entre le passage par référence et le passage par valeur?

Basé sur le post de mikeash :

... Voyons d'abord quelques exemples extrêmes et évidents. Les entiers sont évidemment copiables. Il doit s'agir de types de valeur. Les sockets réseau ne peuvent pas être copiés de manière sensible. Ils doivent être des types de référence. Les points, comme dans les paires x, y, sont copiables. Il doit s'agir de types de valeur. Un contrôleur qui représente un disque ne peut pas être copié de manière sensible. Cela devrait être un type de référence.

Certains types peuvent être copiés, mais ce n'est peut-être pas quelque chose que vous souhaitez obtenir tout le temps. Cela suggère qu'ils devraient être des types de référence. Par exemple, un bouton à l'écran peut être conceptuellement copié. La copie ne sera pas tout à fait identique à l'original. Un clic sur la copie n'activera pas l'original. La copie n'occupera pas le même emplacement à l'écran. Si vous passez le bouton ou le placez dans une nouvelle variable, vous voudrez probablement vous référer au bouton d'origine, et vous ne voudrez faire une copie que lorsque cela est explicitement demandé. Cela signifie que votre type de bouton doit être un type de référence.

Les contrôleurs de vue et de fenêtre sont un exemple similaire. Ils peuvent être copiables, en théorie, mais ce n'est presque jamais ce que vous voudriez faire. Ils doivent être des types de référence.

Qu'en est-il des types de modèles? Vous pouvez avoir un type d'utilisateur représentant un utilisateur sur votre système ou un type de crime représentant une action entreprise par un utilisateur. Ceux-ci sont assez copiables, donc ils devraient probablement être des types de valeur. Cependant, vous voulez probablement que les mises à jour du crime d'un utilisateur effectuées à un endroit de votre programme soient visibles pour les autres parties du programme. Cela suggère que vos utilisateurs devraient être gérés par une sorte de contrôleur utilisateur qui serait un type de référence . par exemple

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

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

Les collections sont un cas intéressant. Il s'agit notamment de tableaux et de dictionnaires, ainsi que de chaînes. Sont-ils copiables? Évidemment. La copie est-elle quelque chose que vous voulez arriver facilement et souvent? C'est moins clair.

La plupart des langues disent «non» à cela et font de leurs collections des types de référence. Cela est vrai dans Objective-C et Java et Python et JavaScript et dans presque tous les autres langages auxquels je peux penser. (Une exception majeure est C ++ avec les types de collection STL, mais C ++ est le fou fou du monde des langues qui fait tout étrangement.)

Swift a dit «oui», ce qui signifie que les types comme Array et Dictionary et String sont des structures plutôt que des classes. Ils sont copiés lors de l'affectation et lors de leur transmission en tant que paramètres. Il s'agit d'un choix tout à fait judicieux tant que la copie est bon marché, ce que Swift s'efforce de réaliser. ...

Personnellement, je ne nomme pas mes cours comme ça. Je nomme généralement le mien UserManager au lieu de UserController mais l'idée est la même

De plus, n'utilisez pas de classe lorsque vous devez remplacer chaque instance d'une fonction, c'est-à-dire qu'elles n'ont aucune fonctionnalité partagée .

Donc, au lieu d'avoir plusieurs sous-classes d'une classe. Utilisez plusieurs structures conformes à un protocole.


Un autre cas raisonnable pour les structures est lorsque vous voulez faire un delta / diff de votre ancien et nouveau modèle. Avec les types de références, vous ne pouvez pas le faire hors de la boîte. Avec les types de valeur, les mutations ne sont pas partagées.

Mon chéri
la source
1
Exactement le genre d'explication que je cherchais. Belle écriture :)
androCoder-BD
Exemple de contrôleur très utile
Demandez à P
1
@AskP J'ai envoyé un e-mail à Mike lui-même et j'ai obtenu ce morceau de code supplémentaire :)
Honey
18

Quelques avantages:

  • threadsafe automatiquement en raison de ne pas être partageable
  • utilise moins de mémoire en raison de l'absence d'isa et de refcount (et en fait, la pile est allouée généralement)
  • les méthodes sont toujours distribuées de manière statique, elles peuvent donc être intégrées (bien que @final puisse le faire pour les classes)
  • plus facile à raisonner (pas besoin de "copier défensivement" comme c'est le cas avec NSArray, NSString, etc ...) pour la même raison que la sécurité des threads
Catfish_Man
la source
Vous ne savez pas si cela sort du cadre de cette réponse, mais pouvez-vous expliquer (ou lier, je suppose) le point «les méthodes sont toujours distribuées statiquement»?
Dan Rosenstark
2
Sûr. Je peux également y ajouter une mise en garde. Le but de la répartition dynamique est de choisir une implémentation lorsque vous ne savez pas d'avance laquelle utiliser. Dans Swift, cela peut être dû à l'héritage (peut être remplacé dans une sous-classe) ou à la fonction générique (vous ne savez pas quel sera le paramètre générique). Les structures ne peuvent pas être héritées et l'optimisation du module entier + la spécialisation générique élimine principalement les génériques inconnus, de sorte que les méthodes peuvent simplement être appelées directement plutôt que d'avoir à chercher quoi appeler. Les génériques non spécialisés continuent cependant à effectuer une répartition dynamique des structures
Catfish_Man
1
Merci, bonne explication. Nous attendons donc plus de vitesse d'exécution, ou moins d'ambiguïté du point de vue IDE, ou les deux?
Dan Rosenstark
1
Surtout l'ancien.
Catfish_Man
Juste une note que les méthodes ne sont pas distribuées statiquement si vous référez la structure via un protocole.
Cristik
12

La structure est beaucoup plus rapide que la classe. De plus, si vous avez besoin d'héritage, vous devez utiliser Class. Le point le plus important est que la classe est le type de référence tandis que la structure est le type de valeur. par exemple,

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  
}

permet maintenant de créer une instance des deux.

var flightA = Flight()

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

laisse maintenant passer ces instances à deux fonctions qui modifient l'identifiant, la description, la destination etc.

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

aussi,

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

donc,

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

maintenant, si nous imprimons l'identifiant et la description du flightA, nous obtenons

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

Ici, nous pouvons voir que l'id et la description de FlightA sont modifiés parce que le paramètre passé à la méthode de modification pointe en fait vers l'adresse mémoire de l'objet flightA (type de référence).

maintenant, si nous imprimons l'id et la description de l'instance FLightB que nous obtenons,

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

Ici, nous pouvons voir que l'instance FlightB n'est pas modifiée car dans la méthode modifyFlight2, l'instance réelle de Flight2 est passée plutôt que référence (type de valeur).

Manoj Karki
la source
2
vous n'avez jamais créé d'instance de FLightB
David Seek
1
alors pourquoi parlez-vous de FlightB bro? Here we can see that the FlightB instance is not changed
David Seek
@ManojKarki, excellente réponse. Je voulais juste souligner que vous avez déclaré flightA deux fois alors que je pense que vous vouliez déclarer FlightA, puis FlightB.
ScottyBlades
11

Structssont value typeet Classessontreference type

  • Les types de valeur sont plus rapides que les types de référence
  • Les instances de type valeur sont sûres dans un environnement multithread car plusieurs threads peuvent muter l'instance sans avoir à se soucier des conditions de concurrence ou des blocages
  • Le type de valeur n'a pas de références contrairement au type de référence; il n'y a donc aucune fuite de mémoire.

Utilisez un valuetype lorsque:

  • Vous voulez que les copies aient un état indépendant, les données seront utilisées dans le code sur plusieurs threads

Utilisez un referencetype lorsque:

  • Vous voulez créer un état mutable partagé.

De plus amples informations peuvent également être trouvées dans la documentation Apple

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


Information additionnelle

Les types de valeurs rapides sont conservés dans la pile. Dans un processus, chaque thread a son propre espace de pile, donc aucun autre thread ne pourra accéder directement à votre type de valeur. Par conséquent, aucune condition de concurrence critique, verrous, interblocages ou toute complexité de synchronisation de thread associée.

Les types de valeurs n'ont pas besoin d'allocation de mémoire dynamique ou de comptage de références, deux opérations coûteuses. En même temps, les méthodes sur les types de valeurs sont distribuées statiquement. Celles-ci créent un énorme avantage en faveur des types de valeur en termes de performances.

Pour rappel, voici une liste de Swift

Types de valeur:

  • Struct
  • Enum
  • Tuple
  • Primitives (Int, Double, Bool etc.)
  • Collections (tableau, chaîne, dictionnaire, ensemble)

Types de référence:

  • Classe
  • Tout ce qui vient de NSObject
  • Une fonction
  • Fermeture
casillas
la source
5

Répondant à la question du point de vue des types de valeur par rapport aux types de référence, ce blog d'Apple semble très simple:

Utilisez un type de valeur [par exemple, struct, enum] lorsque:

  • La comparaison des données d'instance avec == est logique
  • Vous souhaitez que les copies aient un état indépendant
  • Les données seront utilisées dans le code sur plusieurs threads

Utilisez un type de référence [par exemple, classe] lorsque:

  • La comparaison de l'identité d'instance avec === est logique
  • Vous souhaitez créer un état mutable partagé

Comme mentionné dans cet article, une classe sans propriétés inscriptibles se comportera de manière identique avec une structure, avec (j'ajouterai) une mise en garde: les structures sont les meilleures pour les modèles thread-safe - une exigence de plus en plus imminente dans l'architecture d'application moderne.

David James
la source
3

Avec les classes, vous obtenez l'héritage et êtes transmis par référence, les structures n'ont pas d'héritage et sont transmises par valeur.

Il y a de grandes sessions WWDC sur Swift, cette question spécifique est répondue en détail dans l'une d'entre elles. Assurez-vous de les regarder, car cela vous permettra de vous mettre à jour beaucoup plus rapidement que le guide des langues ou l'iBook.

Joride
la source
Pourriez-vous fournir des liens à partir de ce que vous avez mentionné? Parce que sur la WWDC, il y en a beaucoup parmi lesquels choisir, j'aimerais regarder celui qui parle de ce sujet spécifique
MMachinegun
Pour moi, c'est un bon début ici: github.com/raywenderlich/…
MMachinegun
2
Il parle probablement de cette grande session: la programmation orientée protocole dans Swift. (Liens: vidéo , transcription )
zekel
2

Je ne dirais pas que les structures offrent moins de fonctionnalités.

Bien sûr, le moi est immuable sauf dans une fonction de mutation, mais c'est tout.

L'héritage fonctionne bien tant que vous vous en tenez à la bonne vieille idée que chaque classe doit être abstraite ou finale.

Implémentez des classes abstraites en tant que protocoles et des classes finales en tant que structures.

La bonne chose à propos des structures est que vous pouvez rendre vos champs mutables sans créer d'état mutable partagé car la copie à l'écriture s'en charge :)

C'est pourquoi les propriétés / champs dans l'exemple suivant sont tous mutables, ce que je ne ferais pas en Java ou en C # ou dans les classes swift .

Exemple de structure d'héritage avec un peu d'utilisation sale et simple en bas dans la fonction nommée "exemple":

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)")
}
yeoman
la source
2

Dans Swift, un nouveau modèle de programmation a été introduit connu sous le nom de programmation orientée protocole.

Modèle de création:

En rapide, Struct est une valeur types qui est automatiquement cloné. Par conséquent, nous obtenons le comportement requis pour implémenter gratuitement le modèle de prototype.

Alors que les classes sont le type de référence, qui n'est pas automatiquement cloné lors de l'affectation. Pour implémenter le modèle prototype, les classes doivent adopter le NSCopyingprotocole.


La copie superficielle duplique uniquement la référence, qui pointe vers ces objets, tandis que la copie profonde duplique la référence de l'objet.


L'implémentation de la copie profonde pour chaque type de référence est devenue une tâche fastidieuse. Si les classes incluent un type de référence supplémentaire, nous devons implémenter un modèle de prototype pour chacune des propriétés de référence. Et puis nous devons réellement copier le graphique d'objet entier en implémentant le NSCopyingprotocole.

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

class Address{
   var street:String
   ...
} 

En utilisant des structures et des énumérations , nous avons simplifié notre code car nous n'avons pas à implémenter la logique de copie.

Balasubramanian
la source
1

De nombreuses API Cocoa nécessitent des sous-classes NSObject, ce qui vous oblige à utiliser la classe. Mais à part cela, vous pouvez utiliser les cas suivants du blog Swift d'Apple pour décider d'utiliser un type de valeur struct / enum ou un type de référence de classe.

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

akshay
la source
0

Un point qui n'attire pas l'attention dans ces réponses est qu'une variable contenant une classe par rapport à une structure peut parfois letpermettre des modifications sur les propriétés de l'objet, alors que vous ne pouvez pas le faire avec une structure.

Ceci est utile si vous ne voulez pas que la variable pointe vers un autre objet, mais que vous devez quand même modifier l'objet, c'est-à-dire dans le cas de plusieurs variables d'instance que vous souhaitez mettre à jour l'une après l'autre. S'il s'agit d'une structure, vous devez permettre à la variable d'être réinitialisée sur un autre objet en utilisant complètement varpour ce faire, car un type de valeur constante dans Swift permet correctement la mutation zéro, tandis que les types de référence (classes) ne se comportent pas de cette façon.

johnbakers
la source
0

Comme les structures sont des types de valeur et que vous pouvez créer très facilement la mémoire qui est stockée dans la pile, la structure peut être facilement accessible et après la portée du travail, elle est facilement désallouée de la mémoire de la pile via pop depuis le haut de la pile. D'un autre côté, la classe est un type de référence qui stocke en tas et les modifications apportées dans un objet de classe auront un impact sur l'autre objet car elles sont étroitement couplées et de type de référence.Tous les membres d'une structure sont publics alors que tous les membres d'une classe sont privés .

L'inconvénient de struct est qu'il ne peut pas être hérité.

Tapash Mollick
la source
-7
  • La structure et la classe sont des types de données définis par l'utilisateur

  • Par défaut, la structure est publique alors que la classe est privée

  • La classe implémente le principe de l'encapsulation

  • Les objets d'une classe sont créés sur la mémoire du tas

  • La classe est utilisée pour la réutilisabilité tandis que la structure est utilisée pour regrouper les données dans la même structure

  • Les membres de données de structure ne peuvent pas être initialisés directement mais ils peuvent être affectés par l'extérieur de la structure

  • Les membres de données de classe peuvent être initialisés directement par le constructeur sans paramètre et attribués par le constructeur paramétré

Ridaa Zahra
la source
2
La pire réponse jamais!
J. Doe
copier coller réponse
jawadAli