iOS e Swift: O que são Classes e Struct, as diferenças e quando usar
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
estruct
; - Qual a diferença entre
reference type
evalue type
; - Quando devo utilizar
class
e quando devo utilizarstruct
.
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.
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.
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:
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!