¿Por qué elegir Struct Over Class?

476

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

bluedevil2k
fuente
11
Las estructuras siempre se copian cuando se pasan en su código y no utilizan el recuento de referencias. fuente: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex
44
Yo diría que las estructuras son más apropiadas para contener datos, no lógica. Para hablar en términos de Java, imagine estructuras como "Objetos de valor".
Vincent Guerci
66
Estoy sorprendido de que en toda esta conversación no se mencione directamente la copia diferida, también conocida como copia perezosa . Cualquier preocupación sobre el rendimiento de la copia de estructura es principalmente discutible debido a este diseño.
David James
3
Elegir una estructura sobre una clase no es una cuestión de opinión. Hay razones específicas para elegir una u otra.
David James
Recomiendo ver por qué Array no es threadSafe . Está relacionado porque las matrices y estructuras son ambos tipos de valor. Todas las respuestas aquí mencionan que con los tipos structs / arrays / value nunca tendrá un problema de seguridad de subprocesos, pero hay un caso de esquina en el que lo hará.
Miel el

Respuestas:

548

De acuerdo con la muy popular charla WWDC 2015, Protocolo orientado a la programación en Swift ( video , transcripción ), 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 copiables porque la copia es mucho más segura que tener múltiples referencias a la misma instancia que sucede 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 fugas de memoria o múltiples subprocesos para acceder / modificar una sola instancia de una variable. (Para los más técnicos, la excepción es cuando se captura una estructura dentro de un cierre porque en realidad se está capturando una referencia a la instancia a menos que se marque explícitamente para ser copiada).

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

La charla presenta estos escenarios donde se prefieren las clases:

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

Implica que las estructuras deberían ser las predeterminadas y las clases deberían ser una alternativa.

Por otro lado, la documentación del lenguaje de programación Swift es algo 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. Al considerar 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 pauta general, considere crear una estructura cuando se aplique 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 referenciarse cuando asigne o pase una instancia de esa estructura.
  • Cualquier propiedad almacenada por la estructura es en sí misma un tipo de valor, que también se esperaría copiar en lugar de referenciar.
  • La estructura no necesita heredar propiedades o comportamiento de otro tipo existente.

Los 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, quizás encapsulando una propiedad de inicio y una propiedad de longitud, ambas de tipo Int.
  • Un punto en un sistema de coordenadas 3D, quizás encapsulando las propiedades x, y y z, cada una de tipo Double.

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

Aquí se afirma que deberíamos usar clases por defecto y usar estructuras solo en circunstancias específicas. En última instancia, debe comprender la implicación del 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 que la documentación de The Swift Programming Language fue escrita antes de la charla de programación orientada al protocolo.

Drewag
fuente
12
@ElgsQianChen el objetivo de este artículo es que la estructura debe elegirse por defecto y la clase solo debe usarse cuando sea necesario. Las estructuras son mucho más seguras y libres de errores, especialmente en un entorno multiproceso. Sí, siempre puede usar una clase en lugar de una estructura, pero las estructuras son preferibles.
drewag
16
@drewag Eso parece ser exactamente lo contrario de lo que dice. Decía que una clase debería ser el valor predeterminado que usa, no una estructura. In practice, this means that most custom data constructs should be classes, not structures.¿Puede explicarme cómo, después de leer eso, obtiene que la mayoría de los conjuntos de datos deben ser estructuras y no clases? Dieron un conjunto específico de reglas cuando algo debería ser una estructura y prácticamente dijeron "todos los demás escenarios una clase es mejor".
Matt
42
La última línea debería decir: "Mi consejo personal es lo contrario de la documentación:" ... ¡y entonces es una gran respuesta!
Dan Rosenstark
55
El libro de Swift 2.2 todavía dice usar clases en la mayoría de las situaciones.
David James
66
Estructura sobre clase definitivamente reduce la complejidad. Pero, ¿cuál es la implicación en el uso de la memoria cuando las estructuras se convierten en la opción predeterminada? Cuando las cosas se copian en todas partes en lugar de referencia, la aplicación debería aumentar el uso de memoria. ¿No debería?
MadNik
164

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 ajustar Intel tipo de datos usando structy class. Estoy usando 10 valores repetidos para reflejar mejor el mundo real, donde tienes múltiples 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 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)")
}

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 Release build en iPhone 6S, iOS 11.2.6, la configuración del compilador Swift es -O -whole-module-optimization:

  • class la versión tardó 2.06 segundos
  • struct la versión 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, que ejecuta Release build en iPhone 6S, iOS 9.3.1, con un promedio de más de 5 ejecuciones, la configuración del compilador Swift es -O -whole-module-optimization:

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

Nota : como alguien mencionó que en escenarios del mundo real, es probable que haya 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):

(Ejecutado en struct / class con 1 campo, no 10)

A partir de Swift 1.2, Xcode 6.3.2, ejecutando Release build en iPhone 5S, iOS 8.3, promedió más de 5 ejecuciones

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

ANTIGUOS RESULTADOS (de tiempo desconocido)

(Ejecutado en struct / class con 1 campo, no 10)

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

  • La classversión tomó 1.10082 segundos
  • La structversión tomó 0.02324 segundos (50 veces más rápido)
Khanh Nguyen
fuente
27
Es cierto, pero parece que copiar un montón de estructuras sería más lento que copiar una referencia a un solo objeto. En otras palabras, es más rápido copiar un solo puntero que copiar un bloque de memoria arbitrariamente grande.
Tylerc230
14
-1 Esta prueba no es un buen ejemplo porque solo hay una única var en la estructura. Tenga en cuenta que si agrega varios valores y un objeto o dos, la versión de estructura se vuelve comparable a la versión de clase. Mientras más variables agregue, más lenta será la versión de estructura.
joshrl
66
@joshrl entendió su punto, pero un ejemplo es "bueno" o no depende de la situación específica. Este código se extrajo de mi propia aplicación, por lo que es un caso de uso válido, y el uso de estructuras mejoró enormemente el rendimiento de mi aplicación. Probablemente no sea un caso de uso común (bueno, el caso de uso común es que, para la mayoría de las aplicaciones, a nadie le importa cuán rápido se pueden pasar los datos, ya que el cuello de botella ocurre en otro lugar, por ejemplo, conexiones de red, de todos modos, la optimización no es eso crítico cuando tiene dispositivos GHz con GB o RAM).
Khanh Nguyen
26
Por lo que yo entiendo, copiar en Swift está optimizado para suceder en el momento de ESCRIBIR. Lo que significa que no se realiza una copia de memoria física a menos que la nueva copia esté a punto de ser modificada.
Matjan
66
Esta respuesta muestra un ejemplo extremadamente trivial, hasta el punto de ser poco realista y, por lo tanto, incorrecto en muchos casos. Una mejor respuesta sería "depende".
Fui robado
60

Similitudes entre estructuras y clases.

Creé gist para esto con ejemplos simples. https://github.com/objc-swift/swift-classes-vs-structures

Y diferencias

1. Herencia.

Las estructuras no se pueden heredar rápidamente. Si tu quieres

class Vehicle{
}

class Car : Vehicle{
}

Ve a una clase.

2. Pase por

Las estructuras rápidas pasan por valor y las instancias de clase pasan por referencia.

Diferencias contextuales

Estructura constante 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 trato de cambiar la x. Es una expresión válida.

point.x = 5

Pero si definía 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é una clase Point en su lugar, esta es una expresión válida. Porque en una clase, la constante inmutable es la referencia a la clase en sí, no a sus variables de instancia (a menos que esas variables se definan como constantes)

MadNik
fuente
Puede heredar estructuras en Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy
12
La esencia anterior es sobre cómo podemos lograr sabores de herencia para la estructura. Verá la sintaxis como. A: B. Es una estructura llamada A implementa un protocolo llamado B. La documentación de Apple menciona claramente que la estructura no admite herencia pura y no lo hace.
MadNik
2
hombre, ese último párrafo tuyo fue genial. Siempre supe que podías cambiar constantes ... pero de vez en cuando veía dónde no podías, así que estaba desconcertado. Esta distinción lo hizo visible
Miel
28

Aquí hay otras razones para considerar:

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

    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 inicializador ...

  1. Los tipos básicos de colección como Arrayson structs. 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. Aparentemente, la inmutabilidad frente a la mutabilidad es un tema enorme, pero mucha gente inteligente piensa que la inmutabilidad, estructuras en este caso, es preferible. Objetos mutables vs inmutables

Dan Rosenstark
fuente
44
Es cierto que obtienes inicializadores automáticos. También obtiene un inicializador vacío cuando todas las propiedades son opcionales. Pero, si tiene una estructura en un Framework, debe escribir el inicializador usted mismo si desea que esté disponible fuera del internalalcance.
Abizern
2
@Abizern confirmado - stackoverflow.com/a/26224873/8047 - y hombre que es molesto.
Dan Rosenstark
2
@Abizern hay grandes razones para todo en Swift, pero cada vez que algo es cierto en un lugar y no en otro, el desarrollador tiene que saber más cosas. Supongo que aquí es donde se supone que debo decir: "¡Es emocionante trabajar en un idioma tan desafiante!"
Dan Rosenstark
44
¿Puedo agregar también que no es la inmutabilidad de las estructuras lo que las hace útiles (aunque es algo muy bueno)? Puede mutar estructuras, pero debe marcar los métodos mutatingpara que sea explícito sobre qué funciones cambian su estado. Pero su naturaleza como tipos de valor es lo importante. Si declaras una estructura con letno puedes invocar ninguna función mutante en ella. El video de WWDC 15 sobre Mejor programación a través de tipos de valor es un excelente recurso para esto.
Abizern
1
Gracias @Abizern, nunca entendí esto antes de leer tu comentario. Para los objetos, let vs. var no es una gran diferencia, pero para las estructuras es enorme. Gracias por señalar esto.
Dan Rosenstark
27

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

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

Según la publicación de mikeash :

... Veamos primero algunos ejemplos extremos y obvios. Los enteros son obviamente copiables. Deben ser tipos de valor. Los sockets de red no se pueden copiar de manera sensata. 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 con sensatez. Eso debería ser un tipo de referencia.

Se pueden copiar algunos tipos, pero es posible que no sea algo que desee que ocurra todo el tiempo. Esto sugiere que deberían ser tipos de referencia. Por ejemplo, un botón en la pantalla puede copiarse conceptualmente. La copia no será 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 coloca en una nueva variable, probablemente querrá consultar el botón original, y solo querrá 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 modelo? 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 tomada 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 del delito de un 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 . p.ej

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

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

Las colecciones son un caso interesante. Estos incluyen cosas como matrices y diccionarios, así como cadenas. ¿Son copiables? Obviamente. ¿Copiar algo que quieres que suceda es fácil y frecuente? Eso está menos claro.

La mayoría de los idiomas dicen "no" a esto y hacen que sus colecciones hagan referencia a tipos. Esto es cierto en Objective-C y Java y Python y JavaScript y en casi cualquier otro lenguaje que se me ocurra. (Una excepción importante es C ++ con tipos de colección STL, pero C ++ es el loco del mundo del lenguaje que hace todo de manera 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 al pasarlos como parámetros. Esta es una opción completamente sensata siempre y cuando la copia sea barata, lo que Swift se esfuerza mucho por lograr. ...

Yo personalmente no llamo así a mis clases. Por lo general, llamo a mi UserManager en lugar de UserController, pero la idea es la misma

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

Entonces, en lugar de tener varias subclases de una clase. Use varias estructuras que se ajusten a un protocolo.


Otro caso razonable para las estructuras es cuando desea hacer un delta / diff de su modelo antiguo y nuevo. Con los tipos de referencias no puede hacer eso de forma inmediata. Con los tipos de valor, las mutaciones no se comparten.

Miel
fuente
1
Exactamente el tipo de explicación que estaba buscando. Nice write up :)
androCoder-BD
Ejemplo de controlador muy útil
pregunte a P
1
@AskP Envié un correo electrónico a Mike y obtuve ese código adicional :)
Miel
18

Algunas ventajas:

  • seguro para subprocesos debido a que no se puede compartir
  • usa menos memoria debido a que no hay isa y recuenta (y de hecho, la pila se asigna generalmente)
  • los métodos siempre se envían estáticamente, por lo que se pueden incorporar (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
Catfish_Man
fuente
No estoy seguro si está fuera del alcance de esta respuesta, pero ¿puede explicar (o vincular, supongo) los puntos de "los métodos siempre se envían estáticamente"?
Dan Rosenstark
2
Por supuesto. También puedo adjuntarle una advertencia. El propósito del despacho dinámico es elegir una implementación cuando no sabe de antemano cuál usar. En Swift, eso puede deberse a la herencia (podría anularse en una subclase) o debido a que la función es genérica (no sabe cuál será el parámetro genérico). Las estructuras no se pueden heredar y la optimización de todo el módulo + especialización genérica elimina principalmente los genéricos desconocidos, por lo que los métodos se pueden llamar directamente en lugar de tener que buscar qué llamar. Sin embargo, los genéricos no especializados todavía hacen un despacho dinámico para estructuras
Catfish_Man
1
Gracias, gran explicación. ¿Entonces esperamos más velocidad de ejecución, o menos ambigüedad desde una perspectiva IDE, o ambas?
Dan Rosenstark
1
Sobre todo el primero.
Catfish_Man
Solo una nota de que los métodos no se envían estáticamente si hace referencia a la estructura a través de un protocolo.
Cristik
12

La estructura es mucho más rápida que la clase. Además, si necesita herencia, debe usar Class. El punto más importante es que Class es un tipo de referencia, mientras que Structure es un 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 una 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 pasemos esta instancia a dos funciones que modifican la identificación, 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" 
}

entonces,

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

ahora si imprimimos la identificación y la descripción del vuelo A, obtenemos

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

Aquí, podemos ver que la identificación y la descripción de FlightA cambian 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 cambia porque en el método modifyFlight2, la instancia real de Flight2 es pasa en lugar de referencia (tipo de valor).

Manoj Karki
fuente
2
nunca creaste una instancia de FLightB
David Seek
1
entonces, ¿por qué estás hablando de FlightB hermano? Here we can see that the FlightB instance is not changed
David Seek
@ManojKarki, gran respuesta. Solo quería señalar que declaró el vuelo A dos veces cuando creo que quería declarar el vuelo A, luego el vuelo B.
ScottyBlades
11

Structsson value typey Classessonreference type

  • Los tipos de valor son más rápidos que los tipos de referencia
  • Las instancias de tipo de valor son seguras en un entorno de subprocesos múltiples, ya que varios subprocesos pueden mutar la instancia sin tener que preocuparse por las condiciones de carrera o los puntos muertos
  • El tipo de valor no tiene referencias a diferencia del tipo de referencia; Por lo tanto, no hay pérdidas de memoria.

Use un valuetipo cuando:

  • Desea que las copias tengan un estado independiente, los datos se usarán en código en varios subprocesos

Use un referencetipo cuando:

  • Desea crear un estado compartido y mutable.

También se puede encontrar más información en la documentación de Apple

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


Información Adicional

Los tipos de valores rápidos se mantienen en la pila. En un proceso, cada subproceso tiene su propio espacio de pila, por lo que ningún otro subproceso podrá acceder directamente a su tipo de valor. Por lo tanto, no hay condiciones de carrera, bloqueos, puntos muertos o cualquier complejidad relacionada de sincronización de subprocesos.

Los tipos de valor no necesitan asignación de memoria dinámica o recuento de referencias, las cuales son operaciones costosas. Al mismo tiempo, los métodos sobre los tipos de valor se envían estáticamente. Estos crean una gran ventaja a favor de los tipos de valor en términos de rendimiento.

Como recordatorio aquí hay una lista de Swift

Tipos de valor:

  • Estructura
  • Enum
  • Tupla
  • Primitivas (Int, Double, Bool, etc.)
  • Colecciones (matriz, cadena, diccionario, conjunto)

Tipos de referencia:

  • Clase
  • Todo lo que provenga de NSObject
  • Función
  • Cierre
casillas
fuente
5

Respondiendo la pregunta desde la perspectiva de los tipos de valor frente a los tipos de referencia, desde 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
  • Desea que las copias tengan un estado independiente
  • Los datos se usarán en código a través de múltiples hilos

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

  • Comparar la identidad de la instancia con === tiene sentido
  • Desea crear un estado compartido y mutable

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 las mejores para los modelos seguros para subprocesos , un requisito cada vez más inminente en la arquitectura moderna de aplicaciones.

David James
fuente
3

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

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

Joride
fuente
¿Podría proporcionar algunos enlaces de lo que mencionó? Porque en WWDC hay bastantes para elegir, me gustaría ver el que habla sobre este tema específico
MMachinegun
Para mí, este es un buen comienzo aquí: github.com/raywenderlich/…
MMachinegun
2
Probablemente esté hablando de esta gran sesión: programación orientada a protocolos en Swift. (Enlaces: video , transcripción )
zekel
2

No diría que las estructuras ofrecen menos funcionalidad.

Claro, self es inmutable, excepto en una función mutante, pero eso es todo.

La herencia funciona bien siempre y cuando mantengas la vieja idea de que cada clase debe ser abstracta o final.

Implemente 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 directo en la parte inferior de la función llamada "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)")
}
hacendado
fuente
2

En Swift, se ha introducido un nuevo patrón de programación conocido como programación orientada al protocolo.

Patrón creacional:

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 asignación. Para implementar el patrón prototipo, las clases deben adoptar el NSCopyingprotocolo.


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


Implementar una copia profunda para cada tipo de referencia se ha convertido en una tarea tediosa. Si las clases incluyen otro tipo de referencia, tenemos que implementar un patrón prototipo para cada una de las propiedades de referencia. Y luego tenemos que copiar todo el gráfico del objeto implementando el NSCopyingprotocolo.

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

class Address{
   var street:String
   ...
} 

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

Balasubramanian
fuente
1

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

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

akshay
fuente
0

Un punto que no llama la atención en estas respuestas es que una variable que contiene una clase frente a una estructura puede tardar un lettiempo y permitir cambios en las propiedades del objeto, mientras que no puede hacerlo con una estructura.

Esto es útil si no desea que la variable apunte a otro objeto, pero todavía 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 por completo varpara hacerlo, ya que un tipo de valor constante en Swift permite correctamente la mutación cero, mientras que los tipos de referencia (clases) no se comportan de esta manera.

johnbakers
fuente
0

Como struct son tipos de valor y puede crear la memoria muy fácilmente que se almacena en la pila. Se puede acceder fácilmente a la estructura y, después del alcance del trabajo, se desasigna fácilmente de la memoria de la pila a través del pop desde la parte superior de la pila. Por otro lado, la 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 son de 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.

Tapash Mollick
fuente
-7
  • 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 usa para la reutilización, mientras que la estructura se usa para agrupar los datos en la misma estructura

  • Los miembros de datos de estructura no pueden inicializarse directamente, pero pueden ser asignados por el exterior de la estructura

  • Los miembros de datos de clase pueden ser inicializados directamente por el constructor sin parámetros y asignados por el constructor parametrizado

Ridaa Zahra
fuente
2
¡La peor respuesta de todas!
J. Doe
copiar y pegar respuesta
jawadAli