Por que escolher Struct sobre classe?

476

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?

bluedevil2k
fonte
11
As estruturas são sempre copiadas quando transmitidas ao seu código e não usam a contagem de referência. fonte: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex
4
Eu diria que as estruturas são mais apropriadas para armazenar dados, não a lógica. Para falar em termos de Java, imagine estruturas como "Objetos de Valor".
Vincent Guerci
6
Estou impressionado com toda essa conversa, não há menção direta de cópia na gravação, também conhecida como cópia lenta . Quaisquer preocupações sobre o desempenho da cópia de estrutura são discutidas principalmente por causa desse design.
David James
3
Escolher uma estrutura sobre uma classe não é uma questão de opinião. Existem motivos específicos para escolher um ou outro.
David James
Eu recomendo ver Por que a matriz não é threadSafe . Está relacionado porque matrizes e estruturas são os dois tipos de valor. Todas as respostas aqui mencionam que com tipos de estruturas / matrizes / valor nunca haverá um problema de segurança de thread, mas há um caso de canto em que você terá.
Mel

Respostas:

548

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.

drewag
fonte
12
Quando o ponto principal deste artigo é que struct deve ser escolhido por padrão e a classe deve ser usada apenas quando necessário. As estruturas são muito mais seguras e livres de erros, especialmente em um ambiente multithread. Sim, você sempre pode usar uma classe no lugar de uma estrutura, mas as estruturas são preferíveis.
drewag
16
@ drewag Esse parece ser exatamente o oposto do que está dizendo. Dizia que uma classe deveria ser o padrão que você usa, não uma estrutura. 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".
Matt
42
A última linha deve dizer: "Meu conselho pessoal é o oposto da documentação:" ... e então é uma ótima resposta!
Dan Rosenstark
5
O livro Swift 2.2 ainda diz que usa classes na maioria das situações.
David James
6
Struct over Class é definitivamente reduzir a complexidade. Mas qual é a implicação no uso da memória quando estruturas se torna a opção padrão. Quando as coisas são copiadas em todos os lugares, em vez de referência, deve aumentar o uso de memória pelo aplicativo. Não deveria?
21416 MadNik
164

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 Inttipo de dados usando structe 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 segundos
  • struct 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.159942142s
  • struct 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.788332333s
  • struct 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:

  • o class versão demorou 1.10082 seg
  • A structversão levou 0,02324 s (50 vezes mais rápido)
Khanh Nguyen
fonte
27
É verdade, mas parece que copiar um monte de estruturas seria mais lento do que copiar uma referência a um único objeto. Em outras palavras, é mais rápido copiar um único ponteiro do que copiar um bloco de memória arbitrariamente grande.
Tylerc230 26/02
14
-1 Este teste não é um bom exemplo, porque há apenas uma única var na estrutura. Observe que, se você adicionar vários valores e um objeto ou dois, a versão struct será comparável à versão da classe. Quanto mais vars você adicionar, mais lenta será a versão struct.
joshrl
6
@joshrl entendeu seu argumento, mas um exemplo é "bom" ou não depende de uma situação específica. Esse código foi extraído do meu próprio aplicativo, por isso é um caso de uso válido e o uso de estruturas aprimorou enormemente o desempenho do meu aplicativo. Provavelmente não é um caso de uso comum (bem, o caso de uso comum é que, para a maioria dos aplicativos, ninguém se importa com a rapidez com que os dados podem ser transmitidos, já que o gargalo acontece em outro lugar, por exemplo, conexões de rede, de qualquer forma, a otimização não é tão boa assim. crítico quando você possui dispositivos de GHz com GBs ou RAM).
Khanh Nguyen
26
Tanto quanto eu entendo que a cópia rápida é otimizada para acontecer no momento da gravação. O que significa que não há cópia de memória física feita, a menos que a nova cópia esteja prestes a ser alterada.
Matjan
6
Esta resposta está mostrando um exemplo extremamente trivial, a ponto de não ser realista e, portanto, incorreto para muitas instâncias. Uma resposta melhor seria "depende".
iwasrobbed
60

Semelhanças entre estruturas e classes.

Eu criei o gist para isso com exemplos simples. https://github.com/objc-swift/swift-classes-vs-structures

E diferenças

1. Herança.

estruturas não podem herdar rapidamente. Se você quiser

class Vehicle{
}

class Car : Vehicle{
}

Vá para uma aula.

2. Passe por

As estruturas Swift passam por valor e as instâncias de classe passam por referência.

Diferenças contextuais

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)

MadNik
fonte
Você pode herdar estruturas no Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy
12
A essência acima é sobre como podemos obter sabores de herança para struct. Você verá a sintaxe como. R: B. É um struct chamado A implementa um protocolo chamado B. A documentação da Apple menciona claramente que o struct não suporta herança pura e não.
MadNik 16/09/2015
2
homem que seu último parágrafo foi ótimo. Eu sempre soube que você podia mudar constantes ... mas de vez em quando eu via onde você não podia, então fiquei perplexo. Essa distinção tornou visível #
1616
28

Aqui estão alguns outros motivos a considerar:

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

  1. Tipos básicos de coleção como Arraysã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]
  2. 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

Dan Rosenstark
fonte
4
É verdade que você obtém inicializadores automáticos. Você também recebe um inicializador vazio quando todas as propriedades são Opcionais. Mas, se você tem uma estrutura em um Framework, precisa escrever o inicializador, se quiser que esteja disponível fora do internalescopo.
Abizern
2
@Abizern confirmou - stackoverflow.com/a/26224873/8047 - e o homem é irritante.
11116 Dan Rosenstark
2
@ Abizern existem ótimas razões para tudo no Swift, mas toda vez que algo é verdade em um lugar e não em outro, o desenvolvedor precisa saber mais. Acho que é aqui que devo dizer: "é emocionante trabalhar em uma linguagem tão desafiadora!"
Dan Rosenstark 12/03
4
Posso acrescentar também que não é a imutabilidade de estruturas que as torna úteis (embora seja uma coisa muito boa). Você pode alterar estruturas, mas é necessário marcar os métodos mutatingpara 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, letnã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.
Abizern 12/03/16
1
Obrigado @Abizern, eu nunca entendi isso antes de ler seu comentário. Para objetos, let vs. var não faz muita diferença, mas para estruturas é enorme. Obrigado por apontar isso.
Dan Rosenstark 15/03
27

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.

Mel
fonte
1
Exatamente o tipo de explicação que eu estava procurando. Bom escrever:)
androCoder-BD
Exemplo de controlador muito útil
Ask P
1
@AskP Eu enviei um e-mail para o próprio Mike e recebi esse código extra :)
Honey
18

Algumas vantagens:

  • threadsafe automaticamente devido a não ser compartilhável
  • usa menos memória devido a ausência de isa e refcount (e na verdade é geralmente alocado por pilha)
  • Os métodos são sempre enviados estaticamente, portanto, podem ser incorporados (embora o @final possa fazer isso para as classes)
  • mais fácil de raciocinar (não é necessário "copiar defensivamente", como é típico no NSArray, NSString, etc ...) pelo mesmo motivo que a segurança do thread
Catfish_Man
fonte
Não tenho certeza se está fora do escopo desta resposta, mas você pode explicar (ou vincular, eu acho) o ponto "métodos são sempre enviados estaticamente"?
Dan Rosenstark 12/03
2
Certo. Também posso anexar uma advertência a ele. O objetivo do envio dinâmico é escolher uma implementação quando você não sabe antecipadamente qual usar. No Swift, isso pode ser devido à herança (pode ser substituído em uma subclasse) ou devido à função ser genérica (você não sabe qual será o parâmetro genérico). Estruturas não podem ser herdadas e a otimização de módulo inteiro + especialização genérica elimina principalmente os genéricos desconhecidos; portanto, os métodos podem ser chamados diretamente, em vez de ter que procurar o que chamar. Genéricos não especializadas ainda fazem expedição dinâmica para estruturas embora
Catfish_Man
1
Obrigado, ótima explicação. Então, esperamos mais velocidade de execução ou menos ambiguidade da perspectiva do IDE, ou ambos?
Dan Rosenstark 15/03
1
Principalmente o primeiro.
Catfish_Man
Apenas observe que os métodos não são estaticamente despachados se você consultar a estrutura por meio de um protocolo.
Cristik
12

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

Manoj Karki
fonte
2
você nunca criou uma instância do FLightB
David Seek
1
então por que você está falando sobre o FlightB, mano? Here we can see that the FlightB instance is not changed
David Procure
@ManojKarki, ótima resposta. Só queria ressaltar que você declarou flightA duas vezes quando acho que queria declarar FlightA, depois FlightB.
precisa saber é o seguinte
11

Structssão value typee Classessãoreference type

  • Tipos de valor são mais rápidos que tipos de referência
  • Instâncias de tipo de valor são seguras em um ambiente com vários threads, pois vários threads podem alterar a instância sem precisar se preocupar com as condições de corrida ou conflitos
  • O tipo de valor não tem referências diferentes do tipo de referência; portanto, não há vazamentos de memória.

Use um valuetipo quando:

  • Você deseja que as cópias tenham um estado independente; os dados serão usados ​​no código em vários segmentos

Use um referencetipo quando:

  • Você deseja criar um estado compartilhado e mutável.

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:

  • Struct
  • Enum
  • Tuple
  • Primitivas (Int, Double, Bool etc.)
  • Coleções (matriz, sequência, dicionário, conjunto)

Tipos de referência:

  • Classe
  • Qualquer coisa vinda do NSObject
  • Função
  • Fecho
casillas
fonte
5

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.

David James
fonte
3

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.

Joride
fonte
Você poderia fornecer alguns links do que você mencionou? Porque na WWDC existem muito poucos para escolher, eu gostaria de assistir a um a falar sobre este tópico específico
MMachinegun
Para mim, este é um bom começo aqui: github.com/raywenderlich/…
MMachinegun 4/15/15
2
Ele provavelmente está falando sobre esta ótima sessão: Programação Orientada a Protocolo no Swift. (Links: video , transcript )
zekel
2

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

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


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

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.

Balasubramanian
fonte
1

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.

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

akshay
fonte
0

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 letpouco 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 varpara 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.

johnbakers
fonte
0

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.

Tapash Mollick
fonte
-7
  • 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

Ridaa Zahra
fonte
2
Pior resposta de sempre!
11788 J. Doe
Copiar Colocar resposta
jawadAli