Pourquoi Choisir Struct Plutôt Que Class?
15 réponses
selon la très populaire programmation axée sur le protocole talk 2015 de la WWDC dans Swift ( video , transcript ), Swift fournit un certain nombre de fonctionnalités qui rendent les structures meilleures que les classes dans de nombreuses circonstances.
Les structuressont préférables si elles sont relativement petites et copiables parce que la copie est beaucoup plus sûre que d'avoir plusieurs références à la même instance comme cela se produit avec les classes. C'est particulièrement important lors de la transmission d'une variable à plusieurs classes et/ou dans un environnement multithread. Si vous pouvez toujours envoyer une copie de votre variable à d'autres endroits, vous n'avez pas à vous soucier de cet autre endroit changeant la valeur de votre variable en dessous de vous.
avec des structures, il y a beaucoup moins besoin de s'inquiéter de fuites de mémoire ou de plusieurs threads pour accéder/modifier une seule instance d'une variable. (Pour les plus techniquement, l'exception à cela est lors de la capture d'un struct à l'intérieur d'une fermeture parce qu'alors il est en train de capturer une référence à l'instance à moins que vous ne la marquiez explicitement pour qu'elle soit copiée).
Les Classespeuvent aussi être gonflées parce qu'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 de protocoles, vous permet de éliminer le besoin de classes pour atteindre ce genre de comportement.
la présentation expose ces scénarios où les classes sont préférées:
- copier ou comparer des instances n'a pas de sens (par ex. fenêtre)
- la durée de vie de L'Instance est liée à des effets externes (p.ex. fichier temporaire)
- les Instances ne sont que des "éviers"--des conduits en écriture seulement vers un état externe (E. G. CGContext)
implique que les structures devraient être la valeur par défaut et les classes devraient être un repli.
d'autre part, le langage de programmation Swift la documentation est quelque peu contradictoire:
les instances de Structure sont toujours passées par valeur, et classe les instances sont toujours transmises par référence. Cela signifie qu'ils sont adapté à différents types de tâches. Comme vous considérez les données les constructions et les fonctionnalités dont vous avez besoin pour un projet, décider si chaque donnée de construire devrait être définie comme une classe ou une structure.
comme ligne directrice générale, envisagez de créer une structure lorsqu'une ou plusieurs de ces conditions s'appliquent:
- le but premier de la structure est d'encapsuler quelques valeurs de données relativement simples.
- Il est raisonnable de s'attendre à ce que le les valeurs encapsulées seront copiées plutôt que référencées lorsque vous assignez ou faites circuler exemple de cette structure.
- toutes les propriétés stockées par la structure sont elles-mêmes des types de valeurs, qui devraient aussi ê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.
exemples de bons candidats pour les structures inclure:
- la taille d'une forme géométrique, peut-être encapsuler une propriété de largeur et une propriété de hauteur, les deux de type Double.
- une façon de se référer à des gammes dans une série, peut-être encapsuler une propriété de départ et une propriété de longueur, les deux de type Int.
- un point dans un système de coordonnées 3D, peut-être encapsulant des propriétés x, y et z, chacun de type Double.
In tous les autres cas, définir une classe, et créer des instances de cette classe à gérer et à transmettre par référence. Dans la pratique, cela signifie que la plupart des constructions de données personnalisées devraient être des classes, et non des structures.
ici, il prétend que nous devrions utiliser par défaut des classes et des structures uniquement dans des circonstances spécifiques. En fin de compte, vous devez comprendre l'implication du monde réel des types de valeur par rapport aux types de référence et ensuite vous pouvez prendre une décision éclairée quand utiliser des structures ou des classes. En outre, gardez à l'esprit que ces concepts sont en constante évolution et que la documentation sur le langage de programmation Swift a été écrite avant que la discussion sur la programmation axée sur le Protocole ne soit donnée.
puisque les instances de struct sont attribuées sur la pile, et les instances de classe sont attribuées sur tas, les structures peuvent parfois être drastiquement plus rapides.
cependant, vous devez toujours le mesurer vous-même et décider en fonction de votre cas d'utilisation unique.
prendre en considération l'exemple suivant, qui démontre 2 stratégies d'enroulement Int
type de données en utilisant struct
et class
. J'utilise 10 valeurs répétées sont 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 à l'aide de
// 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) :
à partir de Swift 4.0, Xcode 9.2, lancement de la version construire sur iPhone 6S, iOS 11.2.6, le réglage du compilateur Swift est -O -whole-module-optimization
:
-
class
la version a pris 2.06 secondes -
struct
version a pris 4.17 e-08 secondes (50 000 000 de fois plus rapide)
(je n'ai plus moyen de multiples pistes, car les écarts sont très faibles, moins de 5%)
Note : la différence est beaucoup moins dramatique sans optimisation du module entier. Je serais heureux si quelqu'un peut préciser ce que l'indicateur ne fait.
mise à jour (7 mai 2016) :
à partir de Swift 2.2.1, Xcode 7.3, lancement de la version construire sur iPhone 6S, iOS 9.3.1, moyenne sur 5 cycles, le réglage du compilateur Swift est -O -whole-module-optimization
:
-
class
version a pris 2.159942142 s -
struct
version a pris 5.83 E-08s (37,000,000 fois plus rapide)
Note : comme quelqu'un a mentionné que dans les scénarios du monde réel, il y aura 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 (1 juin 2014):
(Ran sur struct/classe avec 1 terrain, et non pas 10)
selon Swift 1.2, Xcode 6.3.2, lancer la version construire sur iPhone 5S, iOS 8.3, moyenne sur 5 sorties
-
class
version a pris 9.788332333 s -
struct
version a pris 0.010532942 s (900 fois plus rapide)
anciens résultats (de temps inconnu)
(Ran sur struct/classe avec 1 terrain, et non pas 10)
avec libération construire sur mon MacBook Pro:
- Le
class
version a pris 1.10082 sec - Le
struct
version a pris 0.02324 sec (50 fois plus rapide)
similitudes entre les structures et les classes.
j'ai créé gist pour cela avec des exemples simples. https://github.com/objc-swift/swift-classes-vs-structures
et différences
1. Héritage.
les structures ne peuvent pas hériter à swift. Si vous voulez
class Vehicle{
}
class Car : Vehicle{
}
pour un cours.
2. Passer Par
Swift structures passage par valeur et des instances de classe passage par référence.
Différences Contextuelles
constante de structure et variables
exemple (utilisé à 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 valide.
point.x = 5
mais si j'ai défini 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 constant immuable.
si j'ai utilisé un point de classe à la place, c'est une expression valide. Parce que dans une classe constante immuable est la référence à la classe elle-même et non ses variables d'instance (à moins que ces variables définies comme constantes)
Voici d'autres raisons à considérer:
-
structs obtenir automatiquement un initialiseur que vous n'avez pas à maintenir 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 cela dans une classe, vous auriez à ajouter l'initialiseur, et maintenir l'initialiseur...
-
les types de collecte de base comme
Array
sont des structures. Le 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]
-
apparemment immutabilité vs. mutabilité est un sujet énorme, mais beaucoup de gens intelligents pensent immuabilité -- structures dans ce cas -- est préférable. Mutable vs immuable objects
quelques avantages:
- threadsafe automatique 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 généralement attribuée) Les méthodes
- sont toujours statiquement expédiées, donc peuvent être inlignées (bien que @final puisse le faire pour les classes)
- plus facile à raisonner (pas besoin de "copier défensivement" comme C'est typique avec NSArray, NSString, etc...) pour la même raison que la sécurité des fils
si l'on suppose que l'on sait que Struct est un type de valeur et Classe 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, consultez Quelle est la différence entre passer par référence et passer par valeur?
D'après mikeash's post :
... Examinons d'abord quelques exemples extrêmes et évidents. Les entiers sont évidemment copiable. Ils devraient être des types de valeur. Les sockets réseau ne peuvent pas être raisonnablement copié. Il devrait s'agir de types de référence. Points, comme dans x, y paires, sont copiable. Ils devraient être des types de valeur. Un contrôleur de représente un disque ne peut pas être raisonnablement copié. Qui devrait être une référence type.
certains types peuvent être copiés mais ce n'est peut-être pas quelque chose que vous voulez se produisent tout le temps. Ceci suggère qu'ils devraient être de référence type. Par exemple, un bouton sur l'écran peut théoriquement être copié. La copie ne sera pas tout à fait identique à l'original. Un clic sur l' la copie n'active pas l'original. La copie n'occupera pas le même emplacement sur l'écran. Si vous passez le bouton ou le mettez dans un nouvelle variable, vous voudrez probablement consulter le bouton d'origine, et vous ne voulez faire une copie quand il est explicitement demander. Que signifie que votre type de bouton doit être d'un type référence.
Les contrôleurs de fenêtre et de vueen sont un exemple similaire. Ils pourraient être copiable, peut-être, mais ce n'est presque jamais ce que vous voudriez faire. Il devrait s'agir de types de référence.
Qu'en est-il des modèles types? Vous pourriez avoir un type d'Utilisateur représentant un utilisateur sur votre système, ou un type de Crime représentant une action prise par un Utilisateur. Ils sont assez copiables, donc ils devraient probablement la valeur type. Cependant, vous voulez probablement des mises à jour sur le Crime D'un utilisateur un endroit dans votre programme pour être visible aux autres parties du programme. cela suggère que vos utilisateurs devraient être gérés par une sorte d'utilisateur contrôleur qui serait un type de référence .
Collections sont un cas intéressant. Ces inclure des choses comme des tableaux et des dictionnaires, ainsi que des cordes. Sont-ils copiable? Évidemment. Être la copie de quelque chose vous souhaitez réaliser facilement et souvent? C'est moins clair.
la plupart des langues disent "non" à cela et font référence à leurs collections type. C'est vrai dans Objective-C et Java et Python et JavaScript et presque toutes les autres langues que je connais. (Une exception importante est C++ avec les types de collection STL, mais C++ est le fou furieux de la mondial de la langue qui fait tout, étrangement.)
Swift a dit "oui", ce qui signifie que les types comme tableau et dictionnaire et Chaîne sont des structures plutôt que de classes. Elles sont copiées sur cession, et sur les passer comme paramètres. C'est un choix judicieux tant que la copie est bon marché, ce à quoi Swift s'efforce accomplir. ...
de plus n'utilisez pas de classe lorsque vous devez outrepasser chaque instance d'une fonction, c'est-à-dire qu'ils n'ont pas de fonctionnalité partagée .
So au lieu d'avoir plusieurs sous-classes d'une classe. Utilisez plusieurs structures qui sont conformes à un protocole.
Structure est beaucoup plus rapide que la classe. En outre, si vous avez besoin de l'héritage, vous devez utiliser la classe. Le point le plus important est que la classe est le type de référence alors 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 l'instance des deux.
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )
permet maintenant de passer ces instances à deux fonctions qui modifient l'id, 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"
}
,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
maintenant si nous imprimons la carte d'identité et la description du flightA, nous obtenons
id = 200
description = "second flight of Virgin Airlines"
ici, nous pouvons voir l'id et la description de FlightA est modifié parce que le paramètre passé à la méthode modify pointe en fait à 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 de Volb que nous obtenons,
id = 100
description = "first ever flight of Virgin Airlines"
ici nous pouvons voir que L'instance FlightB n'est pas changée parce que dans la méthode modifyFlight2, l'instance effective du Flight2 est passes plutôt que reference ( valeur type).
répondre à la question du point de vue des types de valeur vs types de référence, de Ce Apple blog post il semblerait très simple:
utiliser un type de valeur [p.ex. struct, enum] lorsque:
- la Comparaison des données de l'instance avec == sens
- Vous voulez des copies indépendants de l'état
- les données seront utilisées dans le code à travers plusieurs fils
utiliser un type de référence [p.ex. une classe] lorsque:
- la Comparaison de l'instance d'identité avec === sens
- Vous voulez créer partagé, mutable état
comme mentionné dans cet article, une classe sans propriétés d'écriture se comportera de façon identique avec une structure, avec (j'ajouterai) une mise en garde: les structures sont les meilleures pour thread-safe modèles -- une exigence de plus en plus imminente dans l'architecture moderne des applis.
avec les classes vous recevez l'héritage et êtes passés par référence, les structures n'ont pas l'héritage et sont passés 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 regarder ceux-ci, car il vous permettra de se mettre à jour beaucoup plus rapidement que le guide de langue ou l'iBook.
Je ne dirais pas que les structures offrent moins de fonctionnalité.
bien sûr, le soi est immuable sauf dans une fonction mutante, mais c'est à peu près tout.
héritage fonctionne très bien tant que vous vous en tenez à la bonne vieille idée que chaque classe devrait être soit abstraite ou finale.
implémenter des classes abstraites en tant que protocoles et des classes finales en tant que structures.
la bonne chose au sujet des structures est que vous pouvez faire votre fields mutable sans créer l'état mutable partagé parce que la copie en écriture prend soin de cela:)
C'est pourquoi les propriétés / champs dans l'exemple suivant sont tous mutables, ce que je ne ferais pas en Java ou C# ou swift classes .
exemple de structure d'héritage avec un peu d'usage sale et simple en bas dans la fonction appelé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)")
}
dans Swift, un nouveau modèle de programmation a été introduit connu sous le nom de programmation axée sur le protocole.
Creational Motif:
dans swift, Struct est un types de valeurs qui sont automatiquement clonés. Par conséquent, nous obtenons le comportement requis pour mettre en œuvre le modèle de prototype gratuitement.
alors que classes sont type de référence, qui n'est pas automatiquement cloné pendant l'affectation. Pour mettre en œuvre le modèle prototype, les classes doivent adopter le protocole NSCopying
.
la copie peu profonde ne reproduit que la référence, qui pointe vers ces objets tandis que copie profonde reproduit la référence de l'objet.
mise en Œuvre copie pour chaque type de référence est devenu une tâche fastidieuse. Si les classes incluent d'autres types de référence, nous devons implémenter un modèle de prototype pour chacune des propriétés de référence. Et nous devons ensuite copier le graphe d'objet entier en implémentant le protocole NSCopying
.
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
en utilisant structures et enums , nous avons simplifié notre code car nous n'avons pas à implémenter la logique de copie.
de nombreux API cacao nécessitent des sous-classes NSObject, ce qui vous force à utiliser class. Mais à part cela, vous pouvez utiliser les cas suivants du blog Swift D'Apple pour décider s'il faut utiliser un type de valeur struct / enum ou un type de référence de classe.
un point qui n'attire pas l'attention dans ces réponses est qu'une variable tenant une classe contre une structure peut être une let
tout en permettant des changements sur les propriétés de l'objet, alors que vous ne pouvez pas le faire avec une structure.
c'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 où vous avez plusieurs variables d'instance que vous souhaitez mettre à jour l'une après l'autre. Si c'est une struct, vous devez permettez à la variable d'être réinitialisée à un autre objet en utilisant var
pour ce faire, car un type à valeur constante dans Swift permet correctement une mutation zéro, alors que les types de référence (classes) ne se comportent pas de cette façon.
comme struct sont des types de valeur et vous pouvez créer la mémoire très facilement qui stocke dans la pile.Struct peut être facilement accessible et après la portée du travail il est facilement désalloué de la mémoire de la pile par pop du haut de la pile. D'un autre côté, class est un type de référence qui stocke dans tas et les changements faits dans un objet de classe auront un impact sur un autre objet car ils sont étroitement couplés et type de référence.Tous les membres d'une structure publique, alors que tous les membres d'une classe sont privés.
les inconvénients de struct est qu'il ne peut pas être hérité .
-
Structure et classe sont les types de données défiés par l'utilisateur
-
par défaut, la structure est un public tandis que la classe est privée
-
classe met en œuvre le principe d'encapsulation
-
les objets d'une classe sont créés sur la mémoire de tas
-
la Classe est utilisé pour re convivialité alors que la structure est utilisée pour grouper les données dans la même structure
-
données structurelles attribué par l'extérieur de la structure
Les membres de données de classe -
peuvent être initialisés directement par le paramètre moins constructeur et assigné par le constructeur paramétrisé