Tá acabando!

Compre agora com desconto
Corra enquanto há tempo!

dias

:

horas

:

minutos

:

segundos

iOS e Swift: O que são Classes e Struct, as diferenças e quando usar

Giovanna Moeller
Giovanna Moeller

Compartilhe

Imagem de Capa - um iPhone na sua tela inicial.

Resumindo

Neste artigo, vamos explorar e compreender as diferenças entre class e struct em Swift. Veremos também:

  • A definição e uso de class e struct;
  • Qual a diferença entre reference type e value type;
  • Quando devo utilizar class e quando devo utilizar struct.

Essa é uma dúvida bem recorrente de quem está iniciando no mundo do desenvolvimento iOS. Depois de ler esse artigo, você vai conseguir compreender e decidir qual a melhor estrutura para ser utilizada em determinada situação. Vamos lá?

Pontos em comum entre classes e struct

Classes e structs são duas maneiras diferentes, porém muito similares, de definir uma abstração de um conjunto de objetos que possuem características parecidas. Essas características podem ser propriedades (atributos) ou métodos (funções).

Calma, se você não entendeu muito bem ainda, vou exemplificar melhor! Vamos supor que queremos listar todas as características (propriedades) e comportamentos (métodos) que um cachorro pode ter/fazer. Um cachorro pode ter um nome, idade e uma raça como propriedades. Como métodos, ou seja, suas funcionalidades, podemos ter algo como "latir", certo?

Portanto, com classes e structs, conseguimos criar tipos personalizados, tal como criar estruturas que representam entidades do mundo real, como por exemplo, uma pessoa, um carro, casa etc. É importante ressaltar também que classe e struct não criam objetos, eles servem como referência/estrutura para se criar um objeto a partir deles.

Entendendo isso, podemos, por exemplo, criar uma classe chamada Pessoa, que contém algumas propriedades e métodos, e então instanciar um objeto chamado de pessoa:

class Pessoa {
  var nome = "John";
  var sobrenome = "Doe";
  var idade = 34;

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}
var pessoa = Pessoa()
print(pessoa.nome) // John
pessoa.boasVindas() // Boas-vindas, John Doe

Podemos também criar um construtor, que funciona como um inicializador, para conseguir inicializar as propriedades de uma forma dinâmica.Veja abaixo um exemplo:

class Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  init(nome: String, sobrenome: String, idade: Int) {
    self.nome = nome
    self.sobrenome = sobrenome
    self.idade = idade
  }

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}

var pessoa = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
print(pessoa.nome) // John
pessoa.boasVindas() // Boas-vindas, John Doe

E, agora, vamos criar esse mesmo molde chamado "Pessoa", porém no formato struct:

struct Pessoa {
  var nome = "John"
  var sobrenome = "Doe"
  var idade = 34

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}

var pessoa = Pessoa()
print(pessoa.nome) // John
pessoa.boasVindas() // Boas-vindas, John Doe

Ou então, também utilizando um construtor (inicializador):

struct Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  init(nome: String, sobrenome: String, idade: Int) {
    self.nome = nome
    self.sobrenome = sobrenome
    self.idade = idade
  }

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}

var pessoa = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
print(pessoa.nome) // John
pessoa.boasVindas() // Boas-vindas, John Doe

Perceba que escrevemos da mesma maneira, porém só alteramos a palavra-chave de class para struct.

Ok, mas antes de entender as diferenças, vamos entender o que elas possuem em comum:

  • Ambas definem propriedades (para armazenar dados) e métodos (para estabelecer funcionalidades), como vimos acima;
  • Ambas possuem métodos construtores para criar instâncias com valores iniciais;
  • Elas também podem implementar protocolos. Porém, alguns protocolos específicos podem ser implementados apenas por classes;
  • Podem ser extendidas através de uma palavra reservada chamada extension, para expandir sua funcionalidade além de uma implementação padrão.

Agora, já que entendemos o que classes e structs possuem em comum, vamos ver mais sobre as suas diferenças.

E as diferenças, quais são?

Primeiramente, precisamos entender que existem algumas funcionalidades extras que apenas as classes possuem, a struct não:

  • Classes podem herdar de outras classes, sendo assim, conseguem usar o conceito de herança para herdar atributos/métodos de uma classe pai, ou seja, structs não herdam nada: nem classes nem structs;
  • Possuem o conceito de "desinicializadores", utilizando o método deinit(). Dentro desse método, há um código que é executado quando uma instância de uma classe é destruída;
  • As classes também podem ter uma ou mais referências para uma única instância; Esse conceito de referência vamos ver a seguir, não se preocupem.

A principal diferença entre ambas é que classes são reference types (tipo de referência), enquanto structs são value types (tipo de valor). Ok, mas o que significa isso? Vamos entender a seguir!

Antes de partir para a explicação, quero mostrar pra vocês um caso bem interessante. Veja o código abaixo, utilizando uma struct:

struct Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  init(nome: String, sobrenome: String, idade: Int) {
    self.nome = nome
    self.sobrenome = sobrenome
    self.idade = idade
  }

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}
var pessoa1 = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
var pessoa2 = pessoa1
pessoa1.nome = "Gabriel"
print(pessoa1.nome) // Gabriel
print(pessoa2.nome) // John

Aqui, criamos/instanciamos dois objetos, pessoa1 e pessoa2. Porém, igualamos a variável pessoa2 a pessoa1, para que tenha os mesmos valores. Por fim, trocamos o nome da pessoa1 para "Gabriel". Perceba que o nome da variável pessoa2 continuou "John". Mas olha o que acontece quando usamos uma classe em vez de struct:

class Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  init(nome: String, sobrenome: String, idade: Int) {
    self.nome = nome
    self.sobrenome = sobrenome
    self.idade = idade
  }

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}
var pessoa1 = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
var pessoa2 = pessoa1
pessoa1.nome = "Gabriel"
print(pessoa1.nome) // Gabriel
print(pessoa2.nome) // Gabriel

Veja só: agora, mesmo alterando apenas o nome da variável pessoa1, a mudança também refletiu na variável pessoa2, fazendo com que ambos tenham o nome "Gabriel".

Isso acontece pois classes são reference type, ou seja, são passadas como referência, como mencionado acima. Mas, o que é isso?

Reference Type / Tipo de referência

Quando estamos inicializando um objeto, a RAM aloca um espaço de memória e endereço para ele. Em seguida, atribui seu endereço de memória ao objeto que criamos.

A imagem mostra a variável `pessoa1` apontando para o seu endereço de memória

Ou seja, como os objetos são do tipo referência (já que estão sendo declarados por uma classe), eles estão apontando para o mesmo endereço de memória, portanto, são na verdade os mesmos objetos! Se mudarmos uma de suas propriedades/atributos, o outro objeto também será afetado, pois está apontando para o mesmo endereço.

A imagem mostra a variável `pessoa1`e `pessoa2` apontando para o mesmo endereço de memória, já que houve atribuição entre elas.

Value Type / Tipo de valor

Isso não acontece com structs, já que elas são value type. Significa que, quando atribuímos um valor para outro, cada instância mantém uma cópia exclusiva dos dados. Se você alterar uma instância, a outra não será modificada. Esse é o mesmo comportamento de tipos primitivos, como Int, String, Double, entre outros!

Construtores

Uma outra diferença importante é que usando uma struct, você não precisa usar um construtor para inicializar as variáveis, pois ele já está lá indiretamente. Veja o exemplo abaixo:

struct Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  func boasVindas() {
    print("Boas-vindas, \(nome) \(sobrenome)")
  }
}
var pessoa = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
print(pessoa.idade) // 34

Esse código não gera nenhum problema, já que não precisamos do construtor quando utilizamos uma struct. Porém, veja o erro que acontece quando tentamos mudar para uma classe:

O erro quando utilizamos classe é: "Class 'Pessoa' has no initializers"

Quando usamos classe, é obrigatório um construtor para a inicialização das variáveis.

Modificação de propriedades

Além disso, as structs são imutáveis, então, se quisermos alterar uma propriedade dessa struct dentro de uma função, precisamos adicionar a palavra reservada mutating antes do nome da função. Veja o exemplo abaixo:

struct Pessoa {
  var nome: String
  var sobrenome: String
  var idade: Int

  mutating func mudarNome(novoNome: String) {
    self.nome = novoNome
  }
}

Caso não adicionemos essa palavra reservada, causará o seguinte erro: "Cannot assign to property: 'self' is immutable" (em português, “Não se pode atribuir propriedade, pois ‘self’ é imutável). Já com classes, isso não é necessário.

Imutabilidade com o uso de constantes

A última diferença entre classes e struct é que, quando utilizamos uma struct, não podemos alterar as propriedades de uma instância se ela for declarada com uma constante (let). Veja o exemplo abaixo:

struct Pessoa {
  var nome: String
  // Outras propriedades e métodos...
}
let pessoa1 = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
pessoa1.nome = "Gabriel"

Essa nova atribuição ao nome causará o seguinte erro: "Cannot assign to property: 'pessoa1' is a 'let' constant". Porém, isso não acontece com o uso de classes! Mesmo se declararmos a instância com uma constante, nós podemos ainda alterar suas propriedades!

class Pessoa {
  var nome: String
  // Outras propriedades e métodos...
}
let pessoa1 = Pessoa.init(nome: "John", sobrenome: "Doe", idade: 34)
pessoa1.nome = "Gabriel" // A atribuição ocorre sem problemas

Por que isso acontece apenas com struct? Pois como as classes são reference types, a imutabilidade por referência é não poder modificar o endereço de memória dessa referência, apenas. Mas como structs são do tipo valor, nada pode ser modificado com o uso de constantes.

Agora que já vimos as diferenças entre elas, você deve estar se perguntando: quando devemos usar uma classe e quando devemos usar uma struct?

Quando devo usar classe e quando devo usar struct?

Segundo a própria documentação da Apple (você pode acessá-la aqui), o ideal é utilizar struct por padrão. As structs em Swift são poderosas e possuem muitos recursos. Além disso, é mais seguro quando há a passagem por valor, em vez de passagem por referência (já que nesse caso, todas as instâncias estão conectadas entre si).

Porém, há alguns casos em que o uso de classes é recomendado:

  • Quando você precisar utilizar o conceito de herança, herdar propriedades/métodos de uma outra classe pai;
  • Quando você precisar de interoperabilidade, ou seja, utilizar um código de Objective-C, já que nessa linguagem não existe struct;
  • Quando precisamos controlar a identidade, já que as classes em Swift vêm com uma noção embutida de identidade porque são tipos de referência.

Conclusão

Nesse artigo, vimos sobre as diferenças entre class e struct, duas maneiras diferentes de definir propriedades e métodos para um objeto. Vimos também mais sobre reference type e value type, conceitos importantes para saber no mundo da programação.

Você também pode complementar seus estudos lendo mais sobre na documentação.

Se você tem interesse em outros artigos ou quer conhecer mais sobre o desenvolvimento iOS, basta me procurar aqui: Linkedin.

Quer saber mais sobre iOS? Confira nosso curso para iniciantes aqui na Alura: Swift parte 1 - Desenvolvendo para iOS no Xcode

Até a próxima!

Giovanna Moeller
Giovanna Moeller

Oie, eu sou a Gi! Apaixonada por programação e design. Criadora de conteúdo no youtube e instagram @girl.coding.

Veja outros artigos sobre Mobile