structs swift class struct design-principles

swift - structs - ¿Por qué elegir Struct Over Class?



struct vs class swift (15)

Jugando con Swift, proveniente de un fondo Java, ¿por qué querría elegir un Struct en lugar de una Clase? Parece que son lo mismo, con un Struct que ofrece menos funcionalidad. ¿Por qué elegirlo entonces?


En Swift, se ha introducido un nuevo patrón de programación conocido como Programación Orientada al Protocolo.

Patrón de creación:

En swift, Struct es un tipo de valor que se clona automáticamente. Por lo tanto, obtenemos el comportamiento requerido para implementar el patrón prototipo de forma gratuita.

Mientras que las clases son el tipo de referencia, que no se clona automáticamente durante la tarea. Para implementar el patrón prototipo, las clases deben adoptar el protocolo NSCopying .

La copia superficial solo duplica la referencia, que apunta a esos objetos, mientras que la copia profunda duplica la referencia del objeto.

Implementar una copia en profundidad para cada tipo de referencia se ha convertido en una tarea tediosa. Si las clases incluyen un tipo de referencia adicional, debemos implementar un patrón de prototipo para cada una de las propiedades de referencia. Y luego tenemos que copiar todo el gráfico de objetos implementando el protocolo NSCopying .

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

Al utilizar estructuras y enumeraciones , simplificamos nuestro código ya que no tenemos que implementar la lógica de copia.


Similitudes entre estructuras y clases.

He creado la esencia de esto con ejemplos simples. https://github.com/objc-swift/swift-classes-vs-structures

Y diferencias

1. Herencia.

Las estructuras no pueden heredar en veloz. Si tu quieres

class Vehicle{ } class Car : Vehicle{ }

Ir a una clase

2. pasar

Las estructuras de Swift pasan por valor y las instancias de clase pasan por referencia.

Diferencias contextuales

Constantes y variables

Ejemplo (utilizado en WWDC 2014)

struct Point{ var x = 0.0; var y = 0.0; }

Define una estructura llamada punto.

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

Ahora si intento cambiar la x. Es una expresión válida.

point.x = 5

Pero si definiera un punto como constante.

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

En este caso todo el punto es constante inmutable.

Si usé un punto de clase, esta es una expresión válida. Porque en una clase, una constante inmutable es la referencia a la clase en sí misma, no a sus variables de instancia (a menos que esas variables se definan como constantes)


Algunas ventajas:

  • Hilo de forma automática debido a que no se puede compartir.
  • usa menos memoria debido a que no hay isa ni refcount (y de hecho, la pila se asigna generalmente)
  • los métodos siempre se envían de forma estática, por lo que pueden estar en línea (aunque @final puede hacer esto para las clases)
  • Más fácil de razonar (no es necesario "copiar a la defensiva" como es típico con NSArray, NSString, etc.) por la misma razón que la seguridad de subprocesos

Aquí hay algunas otras razones para considerar:

  1. Las estructuras obtienen un inicializador automático que no tiene que mantener en absoluto en el código.

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

Para obtener esto en una clase, tendría que agregar el inicializador y mantener el intializer ...

  1. Los tipos básicos de colección como Array son estructuras. Cuanto más los use en su propio código, más se acostumbrará a pasar por valor en lugar de referencia. Por ejemplo:

    func removeLast(var array:[String]) { array.removeLast() println(array) // [one, two] } var someArray = ["one", "two", "three"] removeLast(someArray) println(someArray) // [one, two, three]

  2. Al parecer, la inmutabilidad frente a la mutabilidad es un tema enorme, pero mucha gente inteligente piensa que la inmutabilidad, en este caso las estructuras, es preferible. Objetos mutables e inmutables.


Como struct son tipos de valor y puede crear la memoria fácilmente que se almacena en la pila. La estructura puede ser fácilmente accesible y, después del alcance del trabajo, es fácilmente desasignada desde la memoria de la pila hasta el pop desde la parte superior de la pila. Por otro lado, clase es un tipo de referencia que se almacena en el montón y los cambios realizados en un objeto de clase afectarán a otro objeto ya que están estrechamente acoplados y tipo de referencia. Todos los miembros de una estructura son públicos, mientras que todos los miembros de una clase son privados. .

Las desventajas de la estructura es que no se puede heredar.


Con las clases obtiene herencia y se pasa por referencia, las estructuras no tienen herencia y se pasan por valor.

Hay excelentes sesiones de la WWDC en Swift, esta pregunta específica se responde en detalle en una de ellas. Asegúrate de verlos, ya que te ayudará a acelerar mucho más rápido que la Guía de idiomas o el iBook.


Dado que las instancias de estructura se asignan en la pila, y las instancias de clase se asignan en el montón, las estructuras a veces pueden ser drásticamente más rápidas.

Sin embargo, siempre debe medirlo usted mismo y decidir en función de su caso de uso único.

Considere el siguiente ejemplo, que muestra 2 estrategias para envolver el tipo de datos Int usando struct y class . Estoy usando 10 valores repetidos para reflejar mejor el mundo real, donde tiene varios campos.

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

El rendimiento se mide utilizando

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

El código se puede encontrar en https://github.com/knguyen2708/StructVsClassPerformance

ACTUALIZACIÓN (27 de marzo de 2018) :

A partir de Swift 4.0, Xcode 9.2, ejecutando la versión de lanzamiento en el iPhone 6S, iOS 11.2.6, la configuración del compilador Swift es -O -whole-module-optimization :

  • class versión de class tomó 2.06 segundos
  • struct versión de la struct tomó 4.17e-08 segundos (50,000,000 veces más rápido)

(Ya no promedio varias ejecuciones, ya que las variaciones son muy pequeñas, menos del 5%)

Nota : la diferencia es mucho menos dramática sin la optimización completa del módulo. Me alegraría si alguien puede señalar lo que realmente hace la bandera.

ACTUALIZACIÓN (7 de mayo de 2016) :

A partir de Swift 2.2.1, Xcode 7.3, ejecutando la versión de lanzamiento en el iPhone 6S, iOS 9.3.1, se promediaron más de 5 ejecuciones, la configuración de Swift Compiler es -O -whole-module-optimization :

  • versión de class tomó 2.159942142s
  • struct versión de struct tomó 5.83E-08s (37,000,000 veces más rápido)

Nota : como alguien mencionó que en los escenarios del mundo real, probablemente habrá más de 1 campo en una estructura, he agregado pruebas para estructuras / clases con 10 campos en lugar de 1. Sorprendentemente, los resultados no varían mucho.

RESULTADOS ORIGINALES (1 de junio de 2014):

(Se ejecutó en estructura / clase con 1 campo, no 10)

A partir de Swift 1.2, Xcode 6.3.2, ejecutando la versión de lanzamiento en iPhone 5S, iOS 8.3, se promediaron más de 5 ejecuciones

  • versión de class tomó 9.788332333s
  • struct versión de struct tomó 0.010532942s (900 veces más rápido)

RESULTADOS ANTIGUOS (de tiempo desconocido)

(Se ejecutó en estructura / clase con 1 campo, no 10)

Con la versión de lanzamiento en mi MacBook Pro:

  • La versión de class tomó 1.10082 seg.
  • La versión de struct tomó 0.02324 segundos (50 veces más rápido)

De acuerdo con la muy popular Programación Orientada al Protocolo de la WWDC 2015 en Swift ( video , transcript ), Swift proporciona una serie de características que hacen que las estructuras sean mejores que las clases en muchas circunstancias.

Las estructuras son preferibles si son relativamente pequeñas y se pueden copiar porque la copia es mucho más segura que tener múltiples referencias a la misma instancia que ocurre con las clases. Esto es especialmente importante cuando se pasa una variable a muchas clases y / o en un entorno multiproceso. Si siempre puede enviar una copia de su variable a otros lugares, nunca tendrá que preocuparse de que ese otro lugar cambie el valor de su variable debajo de usted.

Con Structs, hay mucho menos necesidad de preocuparse por las fugas de memoria o las carreras de múltiples subprocesos para acceder / modificar una sola instancia de una variable. (Para los más técnicos, la excepción es cuando captura una estructura dentro de un cierre porque en realidad está capturando una referencia a la instancia a menos que la marque explícitamente para copiarla).

Las clases también pueden hincharse porque una clase solo puede heredar de una única superclase. Eso nos anima a crear superclases enormes que abarquen muchas habilidades diferentes que solo están relacionadas de forma flexible. El uso de protocolos, especialmente con extensiones de protocolo donde puede proporcionar implementaciones a los protocolos, le permite eliminar la necesidad de clases para lograr este tipo de comportamiento.

La charla expone estos escenarios donde se prefieren las clases:

  • Copiar o comparar instancias no tiene sentido (por ejemplo, Ventana)
  • La duración de la instancia está vinculada a efectos externos (por ejemplo, TemporaryFile)
  • Las instancias son solo "sumideros": conductos de solo escritura al estado externo (por ejemplo, CGContext)

Implica que las estructuras deben ser las predeterminadas y las clases deben ser una alternativa.

Por otro lado, la documentación de The Swift Programming Language es un tanto contradictoria:

Las instancias de estructura siempre se pasan por valor y las instancias de clase siempre se pasan por referencia. Esto significa que son adecuados para diferentes tipos de tareas. Cuando considere las construcciones de datos y la funcionalidad que necesita para un proyecto, decida si cada construcción de datos debe definirse como una clase o como una estructura.

Como norma general, considere crear una estructura cuando se apliquen una o más de estas condiciones:

  • El propósito principal de la estructura es encapsular algunos valores de datos relativamente simples.
  • Es razonable esperar que los valores encapsulados se copien en lugar de hacer referencia cuando asigne o pase una instancia de esa estructura.
  • Todas las propiedades almacenadas por la estructura son en sí mismas tipos de valor, que también se espera que se copien en lugar de hacer referencia.
  • La estructura no necesita heredar propiedades o comportamiento de otro tipo existente.

Ejemplos de buenos candidatos para estructuras incluyen:

  • El tamaño de una forma geométrica, quizás encapsulando una propiedad de ancho y una propiedad de altura, ambas de tipo Doble.
  • Una forma de referirse a rangos dentro de una serie, tal vez encapsulando una propiedad de inicio y una propiedad de longitud, ambas de tipo Int.
  • Un punto en un sistema de coordenadas 3D, tal vez encapsulando las propiedades x, y y z, cada una de tipo Doble.

En todos los demás casos, defina una clase y cree instancias de esa clase para que sean administradas y pasadas por referencia. En la práctica, esto significa que la mayoría de las construcciones de datos personalizadas deben ser clases, no estructuras.

Aquí se afirma que deberíamos utilizar clases y estructuras de forma predeterminada solo en circunstancias específicas. En última instancia, debe comprender la implicación en el mundo real de los tipos de valor frente a los tipos de referencia y luego puede tomar una decisión informada sobre cuándo usar estructuras o clases. Además, tenga en cuenta que estos conceptos siempre están evolucionando y la documentación de The Swift Programming Language se escribió antes de que se diera la charla de Programación orientada al protocolo.


La estructura es mucho más rápida que la clase. Además, si necesita herencia, entonces debe usar Clase. El punto más importante es que la clase es el tipo de referencia, mientras que la estructura es el tipo de valor. por ejemplo,

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 }

ahora vamos a crear instancia de ambos.

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

ahora permite pasar estas instancias a dos funciones que modifican el id, la descripción, el destino, etc.

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

además,

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

asi que,

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

Ahora si imprimimos la identificación y descripción del flightA, obtenemos

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

Aquí, podemos ver que la identificación y la descripción de FlightA se modifican porque el parámetro pasado al método de modificación en realidad apunta a la dirección de memoria del objeto flightA (tipo de referencia).

Ahora si imprimimos el ID y la descripción de la instancia de FLightB que obtenemos,

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

Aquí podemos ver que la instancia de FlightB no se cambia porque en el método modifyFlight2, la instancia real de Flight2 se pasa en lugar de referencia (tipo de valor).


Muchas API de Cocoa requieren subclases de NSObject, lo que obliga a utilizar la clase. Pero aparte de eso, puede usar los siguientes casos del blog Swift de Apple para decidir si usar un tipo de valor struct / enum o un tipo de referencia de clase.

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


No diría que las estructuras ofrezcan menos funcionalidad.

Claro, el yo es inmutable, excepto en una función de mutación, pero eso es todo.

La herencia funciona bien siempre y cuando se apegue a la idea antigua de que cada clase debe ser abstracta o final.

Implementar clases abstractas como protocolos y clases finales como estructuras.

Lo bueno de las estructuras es que puede hacer que sus campos sean mutables sin crear un estado mutable compartido porque la copia en escritura se encarga de eso :)

Es por eso que las propiedades / campos en el siguiente ejemplo son todos mutables, lo que no haría en Java o C # o clases rápidas.

Ejemplo de estructura de herencia con un poco de uso sucio y sencillo en la parte inferior de la función denominada "ejemplo":

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


Responder a la pregunta desde la perspectiva de los tipos de valor frente a los tipos de referencia, de esta publicación del blog de Apple parecería muy simple:

Utilice un tipo de valor [por ejemplo, struct, enum] cuando:

  • Comparar datos de instancia con == tiene sentido
  • Quieres que las copias tengan estado independiente
  • Los datos se utilizarán en el código en varios subprocesos

Utilice un tipo de referencia [por ejemplo, clase] cuando:

  • Comparar la identidad de instancia con === tiene sentido
  • Quieres crear estado mutable, compartido.

Como se mencionó en ese artículo, una clase sin propiedades de escritura se comportará de manera idéntica con una estructura, con (agregaré) una advertencia: las estructuras son mejores para los modelos seguros para subprocesos , un requisito cada vez más inminente en la arquitectura moderna de aplicaciones.


Suponiendo que sabemos que Struct es un tipo de valor y que Class es un tipo de referencia .

Si no sabe qué tipo de valor y qué tipo de referencia son, consulte ¿Cuál es la diferencia entre pasar por referencia y pasar por valor?

Basado en el post de mikeash :

... Veamos algunos ejemplos extremos y obvios primero. Los enteros son obviamente copiables. Deben ser tipos de valor. Los sockets de red no se pueden copiar con sensatez. Deben ser tipos de referencia. Los puntos, como en los pares x, y, son copiables. Deben ser tipos de valor. Un controlador que representa un disco no se puede copiar sensiblemente. Ese debería ser un tipo de referencia.

Algunos tipos se pueden copiar, pero puede que no sea algo que quiera que suceda todo el tiempo. Esto sugiere que deberían ser tipos de referencia. Por ejemplo, un botón en la pantalla puede ser copiado conceptualmente. La copia no será muy idéntica al original. Un clic en la copia no activará el original. La copia no ocupará la misma ubicación en la pantalla. Si pasa el botón o lo pone en una nueva variable, probablemente querrá referirse al botón original, y solo querría hacer una copia cuando se solicite explícitamente. Eso significa que su tipo de botón debe ser un tipo de referencia.

Los controladores de vista y ventana son un ejemplo similar. Pueden ser copiables, posiblemente, pero casi nunca es lo que querrías hacer. Deben ser tipos de referencia.

¿Qué pasa con los tipos de modelos? Es posible que tenga un tipo de usuario que represente a un usuario en su sistema o un tipo de delito que represente una acción realizada por un usuario. Estos son bastante copiables, por lo que probablemente deberían ser tipos de valor. Sin embargo, es probable que desee que las actualizaciones de un crimen del usuario realizadas en un lugar de su programa sean visibles para otras partes del programa. Esto sugiere que sus Usuarios deberían ser administrados por algún tipo de controlador de usuario que sería un tipo de referencia .

Las colecciones son un caso interesante. Estos incluyen cosas como matrices y diccionarios, así como cadenas. ¿Son copiables? Obviamente. ¿Está copiando algo que quiere que suceda fácilmente y con frecuencia? Eso es menos claro.

La mayoría de los idiomas dicen "no" a esto y hacen que sus colecciones sean tipos de referencia. Esto es cierto en Objective-C y Java y Python y JavaScript y en casi todos los otros lenguajes que se me ocurran. (Una excepción importante es C ++ con tipos de colección STL, pero C ++ es el loco delirante del mundo del lenguaje que hace todo de forma extraña).

Swift dijo "sí", lo que significa que tipos como Array y Dictionary y String son estructuras en lugar de clases. Se copian en la asignación, y en pasarlos como parámetros. Esta es una opción totalmente sensata, siempre y cuando la copia sea barata, lo que Swift intenta muy difícil de lograr. ...

Además, no utilice la clase cuando tenga que anular todas y cada una de las instancias de una función, es decir, que no tengan ninguna funcionalidad compartida .

Así que en lugar de tener varias subclases de una clase. Utilice varias estructuras que se ajusten a un protocolo.


Un punto que no llama la atención en estas respuestas es que una variable que contiene una clase frente a una estructura puede ser un let mientras aún permite cambios en las propiedades del objeto, mientras que usted no puede hacer esto con una estructura.

Esto es útil si no quiere que la variable apunte a otro objeto, pero aún necesita modificar el objeto, es decir, en el caso de tener muchas variables de instancia que desea actualizar una tras otra. Si es una estructura, debe permitir que la variable se restablezca a otro objeto usando var para hacer esto, ya que un tipo de valor constante en Swift permite la mutación cero, mientras que los tipos de referencia (clases) no se comportan de esta manera .


  • Estructura y clase son tipos de datos desafiados por el usuario

  • Por defecto, la estructura es pública, mientras que la clase es privada.

  • La clase implementa el principio de encapsulación.

  • Los objetos de una clase se crean en la memoria del montón

  • La clase se utiliza para la reutilización, mientras que la estructura se utiliza para agrupar los datos en la misma estructura

  • Los miembros de los datos de estructura no pueden inicializarse directamente, pero pueden asignarse por fuera de la estructura

  • Los miembros de datos de clase se pueden inicializar directamente por el parámetro menos constructor y asignados por el constructor parametrizado