Brincando com o Swift, vindo de Java, por que você escolheria um Struct em vez de uma classe? Parece que são a mesma coisa, com um Struct oferecendo menos funcionalidade. Por que escolher então?
Brincando com o Swift, vindo de Java, por que você escolheria um Struct em vez de uma classe? Parece que são a mesma coisa, com um Struct oferecendo menos funcionalidade. Por que escolher então?
Respostas:
De acordo com a muito popular programação orientada a protocolos da WWDC 2015 em Swift ( vídeo , transcrição ), o Swift fornece vários recursos que tornam as estruturas melhores do que as classes em muitas circunstâncias.
Estruturas são preferíveis se forem relativamente pequenas e copiáveis, porque copiar é muito mais seguro do que ter várias referências à mesma instância que acontece com as classes. Isso é especialmente importante ao passar uma variável para muitas classes e / ou em um ambiente multithread. Se você sempre pode enviar uma cópia da sua variável para outros lugares, nunca precisa se preocupar com esse outro lugar, alterando o valor da sua variável abaixo de você.
Com o Structs, há muito menos necessidade de se preocupar com vazamentos de memória ou vários threads disparados para acessar / modificar uma única instância de uma variável. (Para os mais técnicos, a exceção é a captura de uma estrutura dentro de um fechamento, porque na verdade ela está capturando uma referência à instância, a menos que você a marque explicitamente para ser copiada).
As classes também podem ficar inchadas porque uma classe só pode herdar de uma única superclasse. Isso nos encoraja a criar superclasses enormes que abrangem muitas habilidades diferentes que são apenas vagamente relacionadas. O uso de protocolos, especialmente com extensões de protocolo onde você pode fornecer implementações para protocolos, permite eliminar a necessidade de classes para atingir esse tipo de comportamento.
A palestra apresenta esses cenários em que as aulas são preferidas:
- Copiar ou comparar instâncias não faz sentido (por exemplo, Janela)
- O tempo de vida da instância está vinculado a efeitos externos (por exemplo, TemporaryFile)
- Instâncias são apenas "sumidouros" - condutas somente de gravação para o estado externo (por exemplo, CGContext)
Isso implica que as estruturas devem ser o padrão e as classes devem ser um substituto.
Por outro lado, a documentação da linguagem de programação Swift é um tanto contraditória:
Instâncias de estrutura são sempre passadas por valor e instâncias de classe sempre são passadas por referência. Isso significa que eles são adequados para diferentes tipos de tarefas. Ao considerar as construções de dados e a funcionalidade necessárias para um projeto, decida se cada construção de dados deve ser definida como uma classe ou como uma estrutura.
Como orientação geral, considere criar uma estrutura quando uma ou mais destas condições se aplicarem:
- O objetivo principal da estrutura é encapsular alguns valores de dados relativamente simples.
- É razoável esperar que os valores encapsulados sejam copiados em vez de referenciados quando você atribui ou repassa uma instância dessa estrutura.
- Quaisquer propriedades armazenadas pela estrutura são elas próprias tipos de valor, que também devem ser copiados em vez de referenciados.
- A estrutura não precisa herdar propriedades ou comportamento de outro tipo existente.
Exemplos de bons candidatos a estruturas incluem:
- O tamanho de uma forma geométrica, talvez encapsulando uma propriedade width e height, ambos do tipo Double.
- Uma maneira de se referir a intervalos dentro de uma série, talvez encapsulando uma propriedade start e uma propriedade length, ambas do tipo Int.
- Um ponto em um sistema de coordenadas 3D, talvez encapsulando propriedades x, ye z, cada uma do tipo Double.
Em todos os outros casos, defina uma classe e crie instâncias dessa classe para serem gerenciadas e passadas por referência. Na prática, isso significa que a maioria das construções de dados personalizadas devem ser classes, não estruturas.
Aqui está alegando que deveríamos usar classes e usar estruturas apenas em circunstâncias específicas. Por fim, você precisa entender a implicação no mundo real de tipos de valor versus tipos de referência e, em seguida, tomar uma decisão informada sobre quando usar estruturas ou classes. Além disso, lembre-se de que esses conceitos estão sempre evoluindo e a documentação da linguagem de programação Swift foi escrita antes da palestra sobre programação orientada a protocolo.
In practice, this means that most custom data constructs should be classes, not structures.
Você pode me explicar como, depois de ler isso, você entende que a maioria dos conjuntos de dados deve ser estruturas e não classes? Eles deram um conjunto específico de regras quando algo deveria ser uma estrutura e praticamente disseram "todos os outros cenários em que uma classe é melhor".
Como as instâncias struct são alocadas na pilha e as instâncias de classe são alocadas na pilha, as estruturas às vezes podem ser drasticamente mais rápidas.
No entanto, você deve sempre medir por conta própria e decidir com base em seu caso de uso exclusivo.
Considere o exemplo a seguir, que demonstra duas estratégias de quebra de Int
tipo de dados usando struct
e class
. Estou usando 10 valores repetidos para refletir melhor o mundo real, onde você tem vários 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)
}
O desempenho é medido 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)")
}
O código pode ser encontrado em https://github.com/knguyen2708/StructVsClassPerformance
ATUALIZAÇÃO (27 de março de 2018) :
No Swift 4.0, Xcode 9.2, executando a versão compilada no iPhone 6S, iOS 11.2.6, a configuração do Swift Compiler é -O -whole-module-optimization
:
class
versão demorou 2,06 segundosstruct
versão demorou 4,17e-08 segundos (50.000.000 de vezes mais rápido)(Já não tenho média de várias execuções, pois as variações são muito pequenas, abaixo de 5%)
Nota : a diferença é muito menos dramática sem a otimização do módulo inteiro. Ficaria feliz se alguém pudesse apontar o que a bandeira realmente faz.
ATUALIZAÇÃO (7 de maio de 2016) :
A partir do Swift 2.2.1, Xcode 7.3, executando a versão compilada no iPhone 6S, iOS 9.3.1, com média de 5 execuções, a configuração do Swift Compiler é -O -whole-module-optimization
:
class
versão levou 2.159942142sstruct
versão levou 5.83E-08s (37.000.000 vezes mais rápido)Nota : como alguém mencionou que, em cenários do mundo real, provavelmente haverá mais de um campo em uma estrutura, adicionei testes para estruturas / classes com 10 campos em vez de 1. Surpreendentemente, os resultados não variam muito.
RESULTADOS ORIGINAIS (1 de junho de 2014):
(Executado em struct / class com 1 campo, não 10)
A partir do Swift 1.2, Xcode 6.3.2, executando a versão Build no iPhone 5S, iOS 8.3, em média, em 5 execuções
class
versão levou 9.788332333sstruct
versão levou 0.010532942s (900 vezes mais rápido)RESULTADOS ANTIGOS (de tempo desconhecido)
(Executado em struct / class com 1 campo, não 10)
Com a versão compilada no meu MacBook Pro:
class
versão demorou 1.10082 segstruct
versão levou 0,02324 s (50 vezes mais rápido)Eu criei o gist para isso com exemplos simples. https://github.com/objc-swift/swift-classes-vs-structures
estruturas não podem herdar rapidamente. Se você quiser
class Vehicle{
}
class Car : Vehicle{
}
Vá para uma aula.
As estruturas Swift passam por valor e as instâncias de classe passam por referência.
Constante e variáveis estruturais
Exemplo (usado na WWDC 2014)
struct Point{
var x = 0.0;
var y = 0.0;
}
Define uma estrutura chamada Point.
var point = Point(x:0.0,y:2.0)
Agora, se eu tentar mudar o x. É uma expressão válida.
point.x = 5
Mas se eu definisse um ponto como constante.
let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.
Nesse caso, o ponto inteiro é constante imutável.
Se eu usei uma classe Point, essa é uma expressão válida. Como em uma constante imutável de uma classe, a referência à própria classe não é suas variáveis de instância (a menos que essas variáveis sejam definidas como constantes)
Aqui estão alguns outros motivos a considerar:
As estruturas obtêm um inicializador automático que você não precisa manter no 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 obter isso em uma classe, você teria que adicionar o inicializador e manter o inicializador ...
Tipos básicos de coleção como Array
são estruturas. Quanto mais você os usa em seu próprio código, mais você se acostuma a passar por valor e não por referência. Por exemplo:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
Aparentemente, imutabilidade versus mutabilidade é um tópico enorme, mas muitas pessoas inteligentes pensam que a imutabilidade - estruturas neste caso - é preferível. Objetos mutáveis vs imutáveis
internal
escopo.
mutating
para ser explícito sobre quais funções alteram seu estado. Mas sua natureza como tipos de valor é o que é importante. Se você declarar uma estrutura, let
não poderá chamar nenhuma função mutante. O vídeo da WWDC 15 sobre Melhor programação através de tipos de valor é um excelente recurso.
Supondo que sabemos que Struct é um tipo de valor e Class é um tipo de referência .
Se você não souber o que são um tipo de valor e um tipo de referência, consulte Qual é a diferença entre passar por referência e passar por valor?
Com base na publicação de mikeash :
... Vamos ver alguns exemplos extremos e óbvios primeiro. Os números inteiros são obviamente copiáveis. Eles devem ser tipos de valor. Os soquetes de rede não podem ser sensivelmente copiados. Eles devem ser tipos de referência. Os pontos, como nos pares x, y, são copiáveis. Eles devem ser tipos de valor. Um controlador que representa um disco não pode ser copiado com sensibilidade. Esse deve ser um tipo de referência.
Alguns tipos podem ser copiados, mas pode não ser algo que você queira que aconteça o tempo todo. Isso sugere que eles devem ser tipos de referência. Por exemplo, um botão na tela pode ser copiado conceitualmente. A cópia não será muito idêntica ao original. Um clique na cópia não ativará o original. A cópia não ocupará o mesmo local na tela. Se você passar o botão ou colocá-lo em uma nova variável, provavelmente desejará consultar o botão original e só desejará fazer uma cópia quando solicitado explicitamente. Isso significa que seu tipo de botão deve ser um tipo de referência.
Os controladores de vista e janela são um exemplo semelhante. Eles podem ser copiáveis, concebivelmente, mas quase nunca é o que você gostaria de fazer. Eles devem ser tipos de referência.
E os tipos de modelo? Você pode ter um tipo de usuário representando um usuário em seu sistema ou um tipo de crime representando uma ação executada por um usuário. Eles são bastante copiáveis e, portanto, provavelmente devem ser do tipo valor. No entanto, você provavelmente deseja que as atualizações do Crime do usuário feitas em um local do seu programa sejam visíveis para outras partes do programa. Isso sugere que seus usuários devem ser gerenciados por algum tipo de controlador de usuário que seria um tipo de referência . por exemplo
struct User {} class UserController { var users: [User] func add(user: User) { ... } func remove(userNamed: String) { ... } func ... }
Coleções são um caso interessante. Isso inclui coisas como matrizes e dicionários, além de strings. Eles são copiáveis? Obviamente. Copiar é algo que você deseja que ocorra com facilidade e frequência? Isso é menos claro.
A maioria dos idiomas diz "não" a isso e faz com que suas coleções façam referência. Isso é verdade no Objective-C e Java e Python e JavaScript e em quase todas as outras linguagens em que posso pensar. (Uma grande exceção é o C ++ com tipos de coleção STL, mas o C ++ é o lunático delirante do mundo da linguagem que faz tudo de forma estranha.)
Swift disse que sim, o que significa que tipos como Array e Dictionary e String são estruturas e não classes. Eles são copiados na atribuição e transmitidos como parâmetros. Essa é uma escolha totalmente sensata, desde que a cópia seja barata, o que Swift se esforça muito para realizar. ...
Eu, pessoalmente, não nomeio minhas aulas assim. Eu costumo nomear meu UserManager em vez de UserController, mas a ideia é a mesma
Além disso, não use classe quando precisar substituir todas as instâncias de uma função, ou seja, elas não possuem nenhum compartilhamento funcionalidade .
Então, ao invés de ter várias subclasses de uma classe. Use várias estruturas que estejam em conformidade com um protocolo.
Outro caso razoável para estruturas é quando você deseja fazer um delta / diff do seu modelo antigo e novo. Com tipos de referências, você não pode fazer isso imediatamente. Com tipos de valor, as mutações não são compartilhadas.
Algumas vantagens:
A estrutura é muito mais rápida que a classe. Além disso, se você precisar de herança, deverá usar Classe. O ponto mais importante é que Classe é o tipo de referência, enquanto Estrutura é o tipo de valor. por exemplo,
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
}
agora vamos criar instância de ambos.
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )
agora vamos passar essas instâncias para duas funções que modificam o id, descrição, 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"
}
Além disso,
func modifyFlight2(flight2: Flight2) -> Void {
var passedFlight = flight2
passedFlight.id = 200
passedFlight.description = "second flight from virgin airlines"
}
tão,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
Agora, se imprimirmos o ID e a descrição do flightA, obteremos
id = 200
description = "second flight of Virgin Airlines"
Aqui, podemos ver o ID e a descrição do FlightA alterados porque o parâmetro passado para o método modify realmente aponta para o endereço de memória do objeto flightA (tipo de referência).
Agora, se imprimirmos o ID e a descrição da instância FLightB que obtivermos,
id = 100
description = "first ever flight of Virgin Airlines"
Aqui podemos ver que a instância do FlightB não foi alterada, porque no método modifyFlight2, a instância real do Flight2 é passada em vez de referência (tipo de valor).
Here we can see that the FlightB instance is not changed
Structs
são value type
e Classes
sãoreference type
Use um value
tipo quando:
Use um reference
tipo quando:
Informações adicionais também podem ser encontradas na documentação da Apple
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
informação adicional
Os tipos de valores rápidos são mantidos na pilha. Em um processo, cada encadeamento possui seu próprio espaço de pilha, portanto, nenhum outro encadeamento poderá acessar diretamente seu tipo de valor. Portanto, não há condições de corrida, bloqueios, deadlocks ou qualquer complexidade de sincronização de encadeamentos relacionada.
Os tipos de valor não precisam de alocação dinâmica de memória ou contagem de referência, ambas operações caras. Ao mesmo tempo, métodos em tipos de valor são despachados estaticamente. Isso cria uma enorme vantagem em favor dos tipos de valor em termos de desempenho.
Como lembrete, aqui está uma lista de Swift
Tipos de valor:
Tipos de referência:
Respondendo à pergunta da perspectiva de tipos de valor versus tipos de referência, a partir desta publicação no blog da Apple , pareceria muito simples:
Use um tipo de valor [por exemplo, struct, enum] quando:
- Comparar dados da instância com == faz sentido
- Você deseja que as cópias tenham estado independente
- Os dados serão usados no código em vários segmentos
Use um tipo de referência [por exemplo, classe] quando:
- Comparar a identidade da instância com === faz sentido
- Você deseja criar um estado compartilhado e mutável
Como mencionado nesse artigo, uma classe sem propriedades graváveis se comportará de forma idêntica a uma struct, com (acrescentarei) uma ressalva: as estruturas são melhores para modelos seguros para threads - um requisito cada vez mais iminente na arquitetura moderna de aplicativos.
Com as classes, você obtém herança e é passado por referência, as estruturas não têm herança e são passadas por valor.
Há ótimas sessões da WWDC no Swift, essa pergunta específica é respondida em detalhes em um deles. Assista a eles, pois você poderá acelerar muito mais rapidamente do que o Guia de idiomas ou o iBook.
Eu não diria que as estruturas oferecem menos funcionalidade.
Claro, o eu é imutável, exceto em uma função mutante, mas é isso.
A herança funciona bem desde que você mantenha a boa e velha idéia de que toda classe deve ser abstrata ou final.
Implementar classes abstratas como protocolos e classes finais como estruturas.
O bom das estruturas é que você pode tornar seus campos mutáveis sem criar um estado mutável compartilhado, porque a cópia na gravação cuida disso :)
É por isso que as propriedades / campos no exemplo a seguir são todas mutáveis, o que eu não faria nas classes Java ou C # ou swift .
Exemplo de estrutura de herança com um pouco de uso sujo e direto na parte inferior da função chamada "exemplo":
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)")
}
No Swift, um novo padrão de programação foi introduzido, conhecido como Programação Orientada a Protocolo.
Padrão de criação:
Em rápida, Struct é um tipo de valor que é automaticamente clonado. Portanto, obtemos o comportamento necessário para implementar o padrão de protótipo gratuitamente.
Considerando que as classes são o tipo de referência, que não é automaticamente clonado durante a tarefa. Para implementar o padrão de protótipo, as classes devem adotar o NSCopying
protocolo.
A cópia rasa duplica apenas a referência, que aponta para esses objetos, enquanto a cópia profunda duplica a referência do objeto.
A implementação de cópia profunda para cada tipo de referência tornou-se uma tarefa tediosa. Se as classes incluem outro tipo de referência, temos que implementar o padrão de protótipo para cada uma das propriedades de referência. E então temos que copiar todo o gráfico de objetos implementando o NSCopying
protocolo.
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
Usando structs e enums , tornamos nosso código mais simples, pois não precisamos implementar a lógica de cópia.
Muitas APIs de cacau exigem subclasses NSObject, o que o força a usar a classe. Mas, além disso, você pode usar os seguintes casos do blog Swift da Apple para decidir se deve usar um tipo de valor struct / enum ou um tipo de referência de classe.
Um ponto que não chama a atenção nessas respostas é que uma variável que mantém uma classe versus uma estrutura pode demorar um let
pouco para permitir alterações nas propriedades do objeto, enquanto você não pode fazer isso com uma estrutura.
Isso é útil se você não quiser que a variável aponte para outro objeto, mas ainda precise modificá-lo, ou seja, no caso de ter muitas variáveis de instância que deseja atualizar uma após a outra. Se for uma estrutura, você deve permitir que a variável seja redefinida para outro objeto usando-a var
para fazer isso, pois um tipo de valor constante no Swift permite corretamente uma mutação zero, enquanto os tipos de referência (classes) não se comportam dessa maneira.
Como struct são tipos de valor e você pode criar a memória com muita facilidade que é armazenada na pilha. A estrutura pode ser facilmente acessível e, após o escopo do trabalho, é facilmente desalocada da memória da pilha e pop da parte superior da pilha. Por outro lado, classe é um tipo de referência que armazena no heap e as alterações feitas em um objeto de classe terão impacto em outro objeto, pois são fortemente acoplados e tipo de referência.Todos os membros de uma estrutura são públicos, enquanto todos os membros de uma classe são privados. .
As desvantagens do struct é que ele não pode ser herdado.
Estrutura e classe são tipos de dados desafiados pelo usuário
Por padrão, a estrutura é pública, enquanto a classe é privada.
Classe implementa o principal do encapsulamento
Objetos de uma classe são criados na memória heap
A classe é usada para reutilização, enquanto a estrutura é usada para agrupar os dados na mesma estrutura
Os membros dos dados da estrutura não podem ser inicializados diretamente, mas podem ser atribuídos por fora da estrutura
Membros de dados da classe podem ser inicializados diretamente pelo parâmetro menos construtor e atribuídos pelo construtor parametrizado